#if 0 // 备份
/*
 * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdio.h>
#include <string.h>
//#include <inttypes.h>
#include "sdkconfig.h" //sdk配置
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"           //任务
#include "freertos/message_buffer.h" //使用消息缓冲区必须要有这个头文件
#include "esp_chip_info.h"           //芯片_信息
#include "esp_flash.h"               //内存
#include "esp_system.h"              //系统
#include "esp_spi_flash.h"
#include "driver/uart.h" //串口
#include "esp_log.h"     //日志

#include "gatts_table_creat_demo.h" //ble蓝牙

#include "driver/gpio.h"  //GPIO
#include "driver/ledc.h"  //LEDPWM
#include "driver/mcpwm.h" //电机LEDPWM


static const char *TAG = "example1";
static ledc_timer_config_t dingShiQi; // 定时器

#define led_pin GPIO_NUM_2
#define led_deng_dai GPIO_NUM_12
#define led_deng_dai_gao GPIO_NUM_13

/*串口*/
#define UART1_TX_BUF_SIZE 256 /* 256个字节 */
#define UART1_RX_BUF_SIZE 132 /* 256个字节 */
#define UART1_RX GPIO_NUM_16  /* 256个字节 */
#define UART1_TX GPIO_NUM_17  /* 256个字节 */
static char msg_test[] = "hello,world"; /* 测试使用的字符串 */
static char buffer[UART1_RX_BUF_SIZE];  /* 暂时存储从串口接收到的字符串 */

/*串口初始化*/
void uartInit()
{
    /* 串口1的配置 */
    const uart_config_t uart1_config =
        {
            .baud_rate = 115200,                   /* 通讯波特率 */
            .data_bits = UART_DATA_8_BITS,         /* 每一个数据是8位 */
            .parity = UART_PARITY_DISABLE,         /* 关闭奇偶校验 */
            .stop_bits = UART_STOP_BITS_1,         /* 停止位是1位 */
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, /* 软件控流 */
            .source_clk = UART_SCLK_APB,           /* APB时钟 */
        };
    /* 设置串口1的参数 */
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &uart1_config));

    /* 设置串口的gpio口，esp32支持gpio口动态设置，这一次先使用默认的串口gpio */
    ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, UART1_TX, UART1_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));

    /* 启动串口1 */
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1,        /* 串口1 */
                                        UART1_TX_BUF_SIZE, /* 发送FIFO的大小 */
                                        UART1_RX_BUF_SIZE, /* 接受FIFO的大小 */
                                        0,                 /* 不使用queue */
                                        NULL,              /* 因为不使用queue，所以NULL */
                                        0)                 /* 不分配中断标志 */
    );
}

/*LEDPWM初始化*/
void led_pwm_Config()
{
    ledc_timer_config_t ledc_timer_Structure;
    ledc_timer_Structure.speed_mode = LEDC_HIGH_SPEED_MODE;
    ledc_timer_Structure.timer_num = LEDC_TIMER_0;
    ledc_timer_Structure.duty_resolution = 13; // 分辨率
    ledc_timer_Structure.freq_hz = 5000;
    ledc_timer_Structure.clk_cfg = LEDC_APB_CLK;
    ledc_timer_config(&ledc_timer_Structure);
    ledc_channel_config_t ledc_channel_Structure;
    ledc_channel_Structure.gpio_num = led_deng_dai_gao; // 16
    ledc_channel_Structure.speed_mode = LEDC_HIGH_SPEED_MODE;
    ledc_channel_Structure.channel = LEDC_CHANNEL_0;
    ledc_channel_Structure.intr_type = LEDC_INTR_DISABLE;
    ledc_channel_Structure.timer_sel = 0;
    ledc_channel_Structure.duty = 0;
    ledc_channel_Structure.hpoint = 0;
    ledc_channel_config(&ledc_channel_Structure);
    // 若想改变占空比，用这个。
    // ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 8190 * 0.4); // 负占空比，负值为50/256=19.5%; 0-8190
    // ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
}
/*发送LEDPWM方波*/
void ledPwmTianYao(float zhanKongBi)
{
    ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 8190 * zhanKongBi); // 负占空比，负值为50/256=19.5%; 0-8190
    ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
}

/*MCPWM方波*/
void tianYao(float zhanKongBi)
{
    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, led_deng_dai_gao); // 一台 MCPWM 发生器就足够了
    mcpwm_config_t pwm_config = {
        .frequency = 2000, // 频率=2kHz
        .cmpr_a = 0,       // PWMxA 的占空比 = 0
        .counter_mode = MCPWM_UP_COUNTER,
        .duty_mode = MCPWM_DUTY_MODE_0,
    };
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
    float aaa = 0;
    bool ac = true;
    while (ac)
    {
        ESP_LOGI(TAG, "现在的占空比为: %f", aaa);
        ESP_ERROR_CHECK(mcpwm_set_duty(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_GEN_A, aaa));
        if (aaa > 98)
        {
            ac = false;
        }
        aaa = aaa + 10;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 多线程,定义消息缓冲区句柄
// MessageBufferHandle_t xMessageBuffer;
//const size_t xMessageBufferSizeBytes = 100;
MessageBufferHandle_t xMessageBuffer;
static const size_t xMessageBufferSizeBytes = 100;

// 多线程,创建队列并发布值的任务
void vATask(void *pvParameters)
{
    const uart_port_t uart_num = UART_NUM_1;
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS,
        .rx_flow_ctrl_thresh = 122,
    };
    // Configure UART parameters
    ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
    /*********************/
    size_t xBytesSent;
    uint8_t ucArrayToSend[] = {'0', '1', '2', '3', 0};
    // unsigned char
    char *pcStringToSend = "String to send";
    const TickType_t x100ms = pdMS_TO_TICKS(100); // 延时100ms

    int len = 0;

    while (1)
    { // 消息缓存区发送的第一种方式
        
        len = uart_read_bytes(UART_NUM_1, buffer, (UART1_RX_BUF_SIZE - 1), 20 / portTICK_PERIOD_MS); // 从串口1的RX_FIFO获取字符串
        // 如果读到包的话
        if (len)
        {
            printf("len  =======   %d\n", len);
            buffer[len] = '\0';                                                             // 在结尾加入字符'\0'，
            ESP_LOGI(TAG, "Recv str -> %s , and the length is:%d", buffer, strlen(buffer)); // 打印logo
            // uart_write_bytes(UART_NUM_1, buffer, strlen(buffer));//发送数据到串口1
            //  将数组发送到消息缓冲区，最多阻塞 100 毫秒，以等待消息缓冲区中有足够的可用空间。
            // xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)buffer, sizeof(buffer), x100ms);
            xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)buffer, len + 1, x100ms);
            // printf("buffer  =======   %d\n",strlen(buffer)+1);

            if (xBytesSent != sizeof(buffer))
            {
                // 在缓冲区中有足够的空间用于写入数据之前，对 xMessageBufferSend() 的调用超时。
            }
            // uart_write_bytes(UART_NUM_1, buffer, strlen(buffer));//发送数据到串口1
            memset(buffer, 0, sizeof(buffer)); // 清空内存，等待下一次的串口报文。
        }

        /*
         // 将数组发送到消息缓冲区，最多阻塞 100 毫秒，以等待消息缓冲区中有足够的可用空间。
         xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)ucArrayToSend, sizeof(ucArrayToSend), x100ms);

         if (xBytesSent != sizeof(ucArrayToSend))
         {
             // 在缓冲区中有足够的空间用于写入数据之前，对 xMessageBufferSend() 的调用超时。
         }

         // 消息缓存区发送的第二种方式
         //  将字符串发送到消息缓冲区。如果缓冲区空间不足，则立即返回。strlen 计算字符串长度不含结尾"\0"
         xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)pcStringToSend, strlen(pcStringToSend), 0);
         if (xBytesSent != strlen(pcStringToSend))
         {
         }
         */

        // vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

// 多线程,从队列接收的任务。
void vADifferentTask(void *pvParameters)
{//vTaskDelete(NULL)可以用于删除任务自身。
    uint8_t ucRxData[20];
    size_t xReceivedBytes;
    const TickType_t xBlockTime = pdMS_TO_TICKS(20);

    char *pcStringToSend = "String to send";
    float zkbGaiBian = 0;
    while (1)
    {
        // 接收消息
        // 从消息缓冲区接收下一条消息。在阻塞状态下等待（因此不使用任何 CPU 处理时间）最多 100 毫秒，消息才可用。
        xReceivedBytes = xMessageBufferReceive(xMessageBuffer,
                                               (void *)ucRxData,
                                               sizeof(ucRxData),
                                               xBlockTime);

        if (xReceivedBytes > 0)
        {
            printf(" jie-----shou: ucRxData is %d long. ucRxData : %s \n", xReceivedBytes, ucRxData);
            char zkbJieShou = ucRxData[0];
            switch (zkbJieShou)
            {
            case 'a':
                zkbGaiBian = 20;
                ESP_LOGI(TAG, "现在的占空比为: %f", zkbGaiBian);
                break;
            case 'b':
                zkbGaiBian = 40;
                ESP_LOGI(TAG, "现在的占空比为: %f", zkbGaiBian);
                break;
            case 'c':
                zkbGaiBian = 60;
                ESP_LOGI(TAG, "现在的占空比为: %f", zkbGaiBian);
                break;
            case 'd':
                zkbGaiBian = 80;
                ESP_LOGI(TAG, "现在的占空比为: %f", zkbGaiBian);
                break;
            case 'e':
                zkbGaiBian = 98;
                ESP_LOGI(TAG, "现在的占空比为: %f", zkbGaiBian);
                break;
            case 'f':
                zkbGaiBian = 0;
                ESP_LOGI(TAG, "现在的占空比为: %f", zkbGaiBian);
                break;
            default:
                break;
            }
            // 这里的消息......
            // uart_write_bytes(UART_NUM_1, pcStringToSend, strlen(pcStringToSend));//发送数据到串口1
            // ESP_LOGI(TAG, "Recv shi li  -> %s ,  555555 and the length is:%d", msg_test, strlen(msg_test));
        }
        ledPwmTianYao(zkbGaiBian);
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
}

// GPIO初始化
void gpioInit(void)
{
    gpio_reset_pin(led_pin); // 清除 GPIO 默认的状态
    gpio_reset_pin(led_deng_dai_gao);
    gpio_reset_pin(led_deng_dai);

    // gpio_set_direction(led_deng_dai_gao, GPIO_MODE_OUTPUT); // GPIO 输出模式
    gpio_set_direction(led_pin, GPIO_MODE_OUTPUT);            // GPIO 输出模式
    gpio_set_direction(led_deng_dai, GPIO_MODE_INPUT_OUTPUT); // GPIO 输出模式
}

void app_main(void)
{
    printf("Hello world!\n");

    /****************************************/

    /* *******************Print chip information */
    esp_chip_info_t chip_info;
    uint32_t flash_size;
    esp_chip_info(&chip_info);
    printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
           CONFIG_IDF_TARGET,
           chip_info.cores,
           (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
           (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
           (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
           (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");

    unsigned major_rev = chip_info.revision / 100;
    unsigned minor_rev = chip_info.revision % 100;
    printf("silicon revision v%d.%d, ", major_rev, minor_rev);
    if (esp_flash_get_size(NULL, &flash_size) != ESP_OK)
    {
        printf("Get flash size failed");
        return;
    }

    printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
           (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

    printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
    /***************** Print chip information */
    gpioInit();
    led_pwm_Config();
    
    uartInit();


    // 启动线程
    xMessageBuffer = xMessageBufferCreate(xMessageBufferSizeBytes);
    if (xMessageBuffer == NULL)
    {
        printf("message buffer Create fail!\n");
    }
    else
    {
        printf("message buffer Create successed!\n");
    }
    xTaskCreate(vATask, "vATask", 1024 * 2, NULL, 12, NULL);
    xTaskCreate(vADifferentTask, //指向任务入口函数的指针。任务必须实现为永不返回（即连续循环），或者应该使用 vTaskDelete 函数终止
                "vADifferentTask",//任务的描述性名称。这主要是为了*方便调试。configMAX_TASK_NAME_LEN 定义的最大长度 - 默认 * 为 16。
                1024 * 2, //任务堆栈的大小指定为 * 字节数。请注意，这与普通 FreeRTOS 不同。
                NULL, //将用作正在创建的任务的参数的指针。
                11, //任务应运行的优先级。
                NULL);//用于传回一个句柄，通过该句柄可以引用创建的任务

    app_bt_aa();
   // uart_write_bytes(UART_NUM_1, msg_test, strlen(msg_test)); // 发送数据到串口1

    for (;;)
    {
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
    // int aa = jiafa2(6, 8);
    float zkb = 0;
    // ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 8190 * 0.4); // 负占空比，负值为50/256=19.5%; 0-8190
    // ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
    for (int i = 10; i >= 0; i--)
    {
        printf("Restarting in %d seconds...\n", i);
        // printf("jieguo= %d\n", aa);
        vTaskDelay(1000 / portTICK_PERIOD_MS); // 定时器延时
        gpio_set_level(led_pin, 0);            // 输出低电平
        zkb = zkb + 0.1;

        if (i < 2)
        {
            i = 20;
        }
        if (zkb > 0.9)
        {
            zkb = 0.1;
        }

        vTaskDelay(1000 / portTICK_PERIOD_MS);
        gpio_set_level(led_pin, 1); // 输出高电平
        ESP_LOGI(TAG, "现在的占空比为: %f", zkb);
        ledPwmTianYao(zkb);
        // ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 8190 * 0.4); // 负占空比，负值为50/256=19.5%; 0-8190
        // ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
    }

    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
}

#elif 0 // 备份BTgatts_table_creat_demo.c
/*
 * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

/****************************************************************************
 *
 * This demo showcases creating a GATT database using a predefined attribute table.
 * It acts as a GATT server and can send adv data, be connected by client.
 * Run the gatt_client demo, the client demo will automatically connect to the gatt_server_service_table demo.
 * Client demo will enable GATT server's notify after connection. The two devices will then exchange
 * data.
 *
 ****************************************************************************/

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/message_buffer.h" //使用消息缓冲区必须要有这个头文件
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"

#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_main.h"
#include "gatts_table_creat_demo.h"
#include "esp_gatt_common_api.h"

// #include "driver/uart.h"             //串口

#define GATTS_TABLE_TAG "GATTS_TABLE_DEMO"
#define GATTS_TABLE_JS "jie-shou: "
#define GATTS_TABLE_JS2 "jie-shou2: "
#define GATTS_TABLE_JS3 "jie-shou3: "

#define PROFILE_NUM 1
#define PROFILE_APP_IDX 0
#define ESP_APP_ID 0x55
#define SAMPLE_DEVICE_NAME "ESP_GATTS_DEMO" // BT被别的设备发现显示的名字
#define SVC_INST_ID 0

/* 特征值的最大长度。当GATT客户端执行写入或准备写入操作时，数据长度必须小于GATTS_DEMO_CHAR_VAL_LEN_MAX
 */
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500
#define PREPARE_BUF_MAX_SIZE 1024
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))

#define ADV_CONFIG_FLAG (1 << 0)
#define SCAN_RSP_CONFIG_FLAG (1 << 1)

static uint8_t adv_config_done = 0;

uint16_t heart_rate_handle_table[HRS_IDX_NB];

typedef struct
{
    uint8_t *prepare_buf;
    int prepare_len;
} prepare_type_env_t;

static prepare_type_env_t prepare_write_env;

#define CONFIG_SET_RAW_ADV_DATA
#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = {
    /* flags */ /* 0x02数据长度, BT模式- 0x01 模式,0x06数据*/
    0x02, 0x01, 0x06,
    /* tx power*/ /*发射功率: 0x02数据长度, 0a发射功率等级, */
    0x02, 0x0a, 0xeb,
    /* service uuid */ /*uuid 0x03表示后面有3个数据,后一个0x03表示是1个16比特的UUID*/
    0x03, 0x03, 0xFF, 0x00,
    /* device name */ /*BT广播数据里的名字,0x0f 后面的数据长度,0x09BT本地设备名字*/
    0x0f, 0x09, 'E', 'S', 'P', '_', 'G', 'A', 'T', 'T', 'S', '_', 'D', 'E', 'M', 'O'};
static uint8_t raw_scan_rsp_data[] = {
    /* flags */
    0x02, 0x01, 0x06,
    /* tx power */
    0x02, 0x0a, 0xeb,
    /* service uuid */
    0x03, 0x03, 0xFF, 0x00};

#else
static uint8_t service_uuid[16] = {
    /* LSB <--------------------------------------------------------------------------------> MSB */
    // first uuid, 16bit, [12],[13] is the value
    0xfb,
    0x34,
    0x9b,
    0x5f,
    0x80,
    0x00,
    0x00,
    0x80,
    0x00,
    0x10,
    0x00,
    0x00,
    0xFF,
    0x00,
    0x00,
    0x00,
};

/* The length of adv data must be less than 31 bytes */
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = true,
    .min_interval = 0x0006, // slave connection min interval, Time = min_interval * 1.25 msec
    .max_interval = 0x0010, // slave connection max interval, Time = max_interval * 1.25 msec
    .appearance = 0x00,
    .manufacturer_len = 0,       // TEST_MANUFACTURER_DATA_LEN,
    .p_manufacturer_data = NULL, // test_manufacturer,
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = sizeof(service_uuid),
    .p_service_uuid = service_uuid,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// scan response data
static esp_ble_adv_data_t scan_rsp_data = {
    .set_scan_rsp = true,
    .include_name = true,
    .include_txpower = true,
    .min_interval = 0x0006,
    .max_interval = 0x0010,
    .appearance = 0x00,
    .manufacturer_len = 0,       // TEST_MANUFACTURER_DATA_LEN,
    .p_manufacturer_data = NULL, //&test_manufacturer[0],
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = sizeof(service_uuid),
    .p_service_uuid = service_uuid,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
#endif /* CONFIG_SET_RAW_ADV_DATA */

static esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x20,
    .adv_int_max = 0x40,
    .adv_type = ADV_TYPE_IND,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .channel_map = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

struct gatts_profile_inst
{
    esp_gatts_cb_t gatts_cb; // GATT 服务器回调函数类型,函数指针
    uint16_t gatts_if;
    uint16_t app_id;
    uint16_t conn_id;
    uint16_t service_handle;
    esp_gatt_srvc_id_t service_id;
    uint16_t char_handle;
    esp_bt_uuid_t char_uuid;
    esp_gatt_perm_t perm;
    esp_gatt_char_prop_t property;
    uint16_t descr_handle;
    esp_bt_uuid_t descr_uuid;
};

/*此函数为函数指针,事件发生时的回调*/
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
                                        esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);

/*1个基于gatt的配置文件1个app_id和1个gatts_if，该数组将存储ESP_GATTS_REG_EVT返回的gatts_if */
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {
    [PROFILE_APP_IDX] = {
        .gatts_cb = gatts_profile_event_handler,
        .gatts_if = ESP_GATT_IF_NONE, /* 没有得到gatt_if，所以初始是ESP_GATT_IF_NONE */
    },
};

/* Service */
static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF;
static const uint16_t GATTS_CHAR_UUID_TEST_A = 0xFF01;
static const uint16_t GATTS_CHAR_UUID_TEST_B = 0xFF02;
static const uint16_t GATTS_CHAR_UUID_TEST_C = 0xFF03;

static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char_prop_write = ESP_GATT_CHAR_PROP_BIT_WRITE;
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t heart_measurement_ccc[2] = {0x00, 0x00};
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};

/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
    {
        // Service Declaration
        [IDX_SVC] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},

        /* Characteristic Declaration */
        [IDX_CHAR_A] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},

        /* Characteristic Value */
        [IDX_CHAR_VAL_A] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

        /* Client Characteristic Configuration Descriptor */
        [IDX_CHAR_CFG_A] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},

        /* Characteristic Declaration */
        [IDX_CHAR_B] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},

        /* Characteristic Value */
        [IDX_CHAR_VAL_B] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

        /* Characteristic Declaration */
        [IDX_CHAR_C] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}},

        /* Characteristic Value */
        [IDX_CHAR_VAL_C] =
            {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

};

/*回调函数*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event)
    {
#ifdef CONFIG_SET_RAW_ADV_DATA
    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
        adv_config_done &= (~ADV_CONFIG_FLAG);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
    case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
        adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
#else
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        adv_config_done &= (~ADV_CONFIG_FLAG);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
    case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
        adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
#endif
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
        /* advertising start complete event to indicate advertising start successfully or failed */
        if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "advertising start failed");
        }
        else
        {
            ESP_LOGI(GATTS_TABLE_TAG, "advertising start successfully");
        }
        break;
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
        if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed");
        }
        else
        {
            ESP_LOGI(GATTS_TABLE_TAG, "Stop adv successfully\n");
        }
        break;
    case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
                 param->update_conn_params.status,
                 param->update_conn_params.min_int,
                 param->update_conn_params.max_int,
                 param->update_conn_params.conn_int,
                 param->update_conn_params.latency,
                 param->update_conn_params.timeout);
        break;
    default:
        break;
    }
}

void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
{
    ESP_LOGI(GATTS_TABLE_TAG, "prepare write, handle = %d, value len = %d", param->write.handle, param->write.len);
    esp_gatt_status_t status = ESP_GATT_OK;
    if (prepare_write_env->prepare_buf == NULL)
    {
        prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
        prepare_write_env->prepare_len = 0;
        if (prepare_write_env->prepare_buf == NULL)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "%s, Gatt_server prep no mem", __func__);
            status = ESP_GATT_NO_RESOURCES;
        }
    }
    else
    {
        if (param->write.offset > PREPARE_BUF_MAX_SIZE)
        {
            status = ESP_GATT_INVALID_OFFSET;
        }
        else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE)
        {
            status = ESP_GATT_INVALID_ATTR_LEN;
        }
    }
    /*send response when param->write.need_rsp is true */
    if (param->write.need_rsp)
    {
        esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
        if (gatt_rsp != NULL)
        {
            gatt_rsp->attr_value.len = param->write.len;
            gatt_rsp->attr_value.handle = param->write.handle;
            gatt_rsp->attr_value.offset = param->write.offset;
            gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
            memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
            esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
            if (response_err != ESP_OK)
            {
                ESP_LOGE(GATTS_TABLE_TAG, "Send response error");
            }
            free(gatt_rsp);
        }
        else
        {
            ESP_LOGE(GATTS_TABLE_TAG, "%s, malloc failed", __func__);
        }
    }
    if (status != ESP_GATT_OK)
    {
        return;
    }
    memcpy(prepare_write_env->prepare_buf + param->write.offset,
           param->write.value,
           param->write.len);
    prepare_write_env->prepare_len += param->write.len;
}

void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
{
    if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf)
    {
        // ESP_LOG_BUFFER_CHAR
        esp_log_buffer_hex(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
    }
    else
    {
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATT_PREP_WRITE_CANCEL");
    }
    if (prepare_write_env->prepare_buf)
    {
        free(prepare_write_env->prepare_buf);
        prepare_write_env->prepare_buf = NULL;
    }
    prepare_write_env->prepare_len = 0;
}

static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    size_t xMessagSizeBytes = 100;
    switch (event)
    {
    case ESP_GATTS_REG_EVT:
    {
        esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
        if (set_dev_name_ret)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "set device name failed, error code = %x", set_dev_name_ret);
        }
#ifdef CONFIG_SET_RAW_ADV_DATA
        esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
        if (raw_adv_ret)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
        }
        adv_config_done |= ADV_CONFIG_FLAG;
        esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
        if (raw_scan_ret)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
        }
        adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#else
        // config adv data
        esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
        if (ret)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret);
        }
        adv_config_done |= ADV_CONFIG_FLAG;
        // config scan response data
        ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
        if (ret)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "config scan response data failed, error code = %x", ret);
        }
        adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#endif
        esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
        if (create_attr_ret)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
        }
    }
    break;
    case ESP_GATTS_READ_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT");
        break;
    case ESP_GATTS_WRITE_EVT:
        if (!param->write.is_prep)
        {
            /********************************************************************************/

            size_t xBytesSent;
            unsigned char *pcString = param->write.value;
            void *zhuanHuan = (void *)pcString;
            char *pcStringToSend = (char *)zhuanHuan;
            const TickType_t x100ms = pdMS_TO_TICKS(100);
            uint8_t ucArrayToSend[] = {'0', '1', '2', '3', 0};
            // 创建消息缓冲区

            /*xMessageBuffer = xMessageBufferCreate(xMessagSizeBytes);
            if (xMessageBuffer == NULL)
            {
                printf("message buffer Create fail!\n");
            }
            else
            {
                printf("message buffer Create successed!\n");
            }*/
            // 将数组发送到消息缓冲区，最多阻塞 100 毫秒，以等待消息缓冲区中有足够的可用空间。
            xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)pcStringToSend, strlen(pcStringToSend), x100ms);

            /*****************/
            // gattc写入的数据长度必须小于GATTS_DEMO_CHAR_VAL_LEN_MAX.
            ESP_LOGI(GATTS_TABLE_JS, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len);
            ESP_LOGI(GATTS_TABLE_JS, " = %s, \n", param->write.value);
            esp_log_buffer_hex(GATTS_TABLE_JS,     // 描述标签
                               param->write.value, // 指向缓冲区数组的指针
                               param->write.len);  // 缓冲区的长度（以字节为单位）
            if (heart_rate_handle_table[IDX_CHAR_CFG_A] == param->write.handle && param->write.len == 2)
            {
                uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];
                if (descr_value == 0x0001)
                {
                    ESP_LOGI(GATTS_TABLE_TAG, "notify enable");
                    uint8_t notify_data[15];
                    for (int i = 0; i < sizeof(notify_data); ++i)
                    {
                        notify_data[i] = i % 0xff;
                    }
                    // notification_data[] 的大小需要小于 MTU 大小
                    esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
                                                sizeof(notify_data), notify_data, false);
                }
                else if (descr_value == 0x0002)
                {
                    ESP_LOGI(GATTS_TABLE_TAG, "indicate enable");
                    uint8_t indicate_data[15];
                    for (int i = 0; i < sizeof(indicate_data); ++i)
                    {
                        indicate_data[i] = i % 0xff;
                    }
                    // the size of indicate_data[] need less than MTU size
                    esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],
                                                sizeof(indicate_data), indicate_data, true);
                }
                else if (descr_value == 0x0000)
                {
                    ESP_LOGI(GATTS_TABLE_TAG, "notify/indicate disable ");
                }
                else
                {
                    ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");
                    esp_log_buffer_hex(GATTS_TABLE_JS2, param->write.value, param->write.len);
                }
            }
            /* send response when param->write.need_rsp is true*/
            if (param->write.need_rsp)
            {
                esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
            }
        }
        else
        {
            /* handle prepare write */
            example_prepare_write_event_env(gatts_if, &prepare_write_env, param);
        }
        break;
    case ESP_GATTS_EXEC_WRITE_EVT:
        // the length of gattc prepare write data must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
        example_exec_write_event_env(&prepare_write_env, param);
        break;
    case ESP_GATTS_MTU_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
        break;
    case ESP_GATTS_CONF_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d", param->conf.status, param->conf.handle);
        break;
    case ESP_GATTS_START_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
        break;
    case ESP_GATTS_CONNECT_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
        esp_log_buffer_hex(GATTS_TABLE_JS3, param->connect.remote_bda, 6);
        esp_ble_conn_update_params_t conn_params = {0};
        memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
        /* For the iOS system, please refer to Apple official documents about the BLE connection parameters restrictions. */
        conn_params.latency = 0;
        conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
        conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
        conn_params.timeout = 400;  // timeout = 400*10ms = 4000ms
        // start sent the update connection parameters to the peer device.
        esp_ble_gap_update_conn_params(&conn_params);
        break;
    case ESP_GATTS_DISCONNECT_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
        esp_ble_gap_start_advertising(&adv_params);
        break;
    case ESP_GATTS_CREAT_ATTR_TAB_EVT:
    {
        if (param->add_attr_tab.status != ESP_GATT_OK)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
        }
        else if (param->add_attr_tab.num_handle != HRS_IDX_NB)
        {
            ESP_LOGE(GATTS_TABLE_TAG, "create attribute table abnormally, num_handle (%d) \
                        doesn't equal to HRS_IDX_NB(%d)",
                     param->add_attr_tab.num_handle, HRS_IDX_NB);
        }
        else
        {
            ESP_LOGI(GATTS_TABLE_TAG, "create attribute table successfully, the number handle = %d\n", param->add_attr_tab.num_handle);
            memcpy(heart_rate_handle_table, param->add_attr_tab.handles, sizeof(heart_rate_handle_table));
            esp_ble_gatts_start_service(heart_rate_handle_table[IDX_SVC]);
        }
        break;
    }
    case ESP_GATTS_STOP_EVT:
    case ESP_GATTS_OPEN_EVT:
    case ESP_GATTS_CANCEL_OPEN_EVT:
    case ESP_GATTS_CLOSE_EVT:
    case ESP_GATTS_LISTEN_EVT:
    case ESP_GATTS_CONGEST_EVT:
    case ESP_GATTS_UNREG_EVT:
    case ESP_GATTS_DELETE_EVT:
    default:
        break;
    }
}

/*事件回调函数*/
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{

    /* 如果事件是注册事件，则存储每个配置文件的 gatts_if */
    if (event == ESP_GATTS_REG_EVT)
    {
        if (param->reg.status == ESP_GATT_OK)
        {
            heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
        }
        else
        {
            ESP_LOGE(GATTS_TABLE_TAG, "reg app failed, app_id %04x, status %d",
                     param->reg.app_id,
                     param->reg.status);
            return;
        }
    }
    do
    {
        int idx;
        for (idx = 0; idx < PROFILE_NUM; idx++)
        {
            /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
            if (gatts_if == ESP_GATT_IF_NONE || gatts_if == heart_rate_profile_tab[idx].gatts_if)
            {
                if (heart_rate_profile_tab[idx].gatts_cb)
                {
                    heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
                }
            }
        }
    } while (0);
}

void app_bt_aa(void)
{
    esp_err_t ret;

    /* 初始化NVS. */
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "%s chu shi hua kong zhi qi: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "%s qi yong kong zhi qi: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_bluedroid_init();
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "%s chu shi hua la ya zi yuan: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_bluedroid_enable();
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "%s qi yong lan ya: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_ble_gatts_register_callback(gatts_event_handler);
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "zhu ce hui diao = %x", ret);
        return;
    }

    ret = esp_ble_gap_register_callback(gap_event_handler);
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "sao miao hui diao = %x", ret);
        return;
    }

    ret = esp_ble_gatts_app_register(ESP_APP_ID);
    if (ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "zu ce ying yong biao shi = %x", ret);
        return;
    }

    esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
    if (local_mtu_ret)
    {
        ESP_LOGE(GATTS_TABLE_TAG, "she zhi ben di MTU = %x", local_mtu_ret);
    }
}

#elif 0

#include <stdio.h>
#define SEEK_SET 0 /* set file offset to offset */

#include <stdio.h>

#include <stdio.h>
// #include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_log.h"
#include "freertos/event_groups.h"

#include "freertos/semphr.h"
// 线程池池部结构体
#define THREAD_NUMBER_MAX 16
typedef struct ST_THREAD_POOL_
{
    int threadNumber;                                  // 当前线程个数
    void *threadReadLock;                              // 当前各个线程读锁
    void *threadWriteLock;                             // 当前操作线程写锁
    void *threadPollQueue;                             // 线程处理队列
    TaskHandle_t threadHandleArray[THREAD_NUMBER_MAX]; // 不用静态数组、可以使用链表

} st_threadPool, *st_threadPool_t;

// 线程池队列式处理
typedef struct ST_THREAD_QUEUE__
{
    int (*function)(void *);
    void *funcArgv;
} st_threadQueum, *st_threadQueum_t;

// 线程池中主处理
static void T_threadPoolFunc(st_threadPool_t tp)
{
    int ret = 0;
    int (*fun)(void *) = NULL;
    st_threadQueum queue = {0};
    while (1)
    {
        xSemaphoreTake(tp->threadReadLock, portMAX_DELAY); // 上锁
        do
        {
            memset(&queue, 0, sizeof(st_threadQueum));
            ret = xQueueReceive(tp->threadPollQueue, &queue, portMAX_DELAY); // 进行队列等待，读取队列数据
        } while (ret == pdFALSE);
        xSemaphoreGive(tp->threadReadLock); // 解锁
        fun = queue.function;
        if (fun)
        {
            ret = (*fun)(queue.funcArgv); // 函数指针回调
            if (ret == -1)
            {
                break;
            } // 返回-1 就会将当前线程释放
        }
    }
    vTaskDelete(NULL);
    return;
}

// 创建线程池任务

void *T_threadPoolCreate(int threadNum, char *name, int stackSize, int priority)
{
    int i = 0;
    int ret = 0;
    char threadName[36] = {0};
    st_threadPool_t tp = (st_threadPool_t)malloc(sizeof(st_threadPool)); // malloc 动态内存分配
    if (!tp)
    {
        goto Err_malloc;
    }

    memset(tp, 0, sizeof(st_threadPool));
    /*
    由于FreeRTOS的新旧版本的API不同，导致现象不同于预期，问题就在xSemaphoreCreateBinary与vSemaphoreCreateBinary的区别
    用vSemaphoreCreateBinary创建的二元信号量，初始值为“满”，因为创建的同时释放了信号量
    需要调用xSemaphoreGive初始化当前信号量
    */
    tp->threadReadLock = xSemaphoreCreateBinary();
    if (!tp->threadReadLock)
    {
        goto Err_rMutex;
    }
    xSemaphoreGive(tp->threadReadLock);

    tp->threadWriteLock = xSemaphoreCreateBinary(); // 创建一个新的二进制信号量实例，并返回一个可以引用新信号量的句柄
    if (!tp->threadWriteLock)
    {
        goto Err_wMutex;
    }
    xSemaphoreGive(tp->threadWriteLock); // 释放信号量

    tp->threadPollQueue = xQueueCreate(threadNum + 1, sizeof(st_threadQueum)); // 创建一个新的队列实例，并返回一个可以引用新队列的句柄
    if (!tp->threadPollQueue)
    {
        goto Err_queue;
    }

    for (i = 0; i < threadNum; i++)
    {
        /*snprintf 是一个C语言中用于格式化输出字符串的函数，它类似于 printf，但是可以将格式化后的字符串输出到一个指定大小的字符数组中，
        从而避免缓冲区溢出的问题。*/
        snprintf(threadName, 36, "tp%s_%d", name, i);
        ret = xTaskCreate(T_threadPoolFunc, threadName, stackSize, tp, priority, &tp->threadHandleArray[i]);
        if (ret != pdPASS)
        {
            goto Err_thFork;
        }
    }
    tp->threadNumber = i;
    return tp;

Err_thFork:
    for (int j = 0; j < i; j++)
    {
        vTaskDelete(&tp->threadHandleArray[i]);
    }
    vQueueDelete(tp->threadPollQueue);
Err_queue:
    vSemaphoreDelete(tp->threadWriteLock);
Err_wMutex:
    vSemaphoreDelete(tp->threadReadLock);
Err_rMutex:
    free(tp);
Err_malloc:
    return NULL;
}

// 等待空闲线程进行任务线程回收
void T_threadPoolDelete(st_threadPool_t tp)
{
    if (!tp)
    {
        return;
    }
    xSemaphoreTake(tp->threadWriteLock, portMAX_DELAY); // 上锁
    tp->threadPollQueue = NULL;
    for (int i = 0; i < tp->threadNumber; i++)
    {
        vTaskDelete(tp->threadHandleArray[i]);
        tp->threadHandleArray[i] = NULL;
    }
    vQueueDelete(tp->threadPollQueue);
    xSemaphoreGive(tp->threadWriteLock);

    vSemaphoreDelete(tp->threadWriteLock);
    tp->threadWriteLock = NULL;
    vSemaphoreDelete(tp->threadReadLock);
    tp->threadReadLock = NULL;
    free(tp);
    tp = NULL;
}

// 向线程池中，发送任务。
int T_threadPoolAddTask(st_threadPool_t tp, int (*func)(void *argv), void *argv, int timeOut_ticks)
{
    int temp = 0;
    st_threadQueum queueObj = {0};
    if (!tp || !tp->threadWriteLock || !tp->threadPollQueue)
    {
        return -1;
    }
    xSemaphoreTake(tp->threadWriteLock, portMAX_DELAY); // 上锁
    queueObj.function = func;
    queueObj.funcArgv = argv;
    temp = xQueueSend(tp->threadPollQueue, &queueObj, timeOut_ticks);
    xSemaphoreGive(tp->threadWriteLock); // 解锁
    return temp;
}

// 测试函数
int test_1(void *argc)
{
    for (int i = 0; i < 100; i++)
    {
        printf("__[%s - %d] \r\n", __FUNCTION__, __LINE__);
        // vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    return 0;
}
int test_2(void *argc)
{
    for (int i = 0; i < 100; i++)
    {
        printf("__[%s - %d] \r\n", __FUNCTION__, __LINE__);
        // vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    return 0;
}
int test_0(void *argc)
{
    for (int i = 0; i < 100; i++)
    {
        printf("__[%s - %d] \r\n", __FUNCTION__, __LINE__);
        // vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    return 0;
}
/*
int app_main( void * argc )
{
    printf("__[%s - %d] \r\n", __FUNCTION__, __LINE__ );
    //os_time_dly( 100 * 5 );
    vTaskDelay(pdMS_TO_TICKS(1000));
    printf("__[%s - %d] \r\n", __FUNCTION__, __LINE__ );
    void* tp_fd = T_threadPoolCreate( 4 , "thTest" , 2 * 1024 , 10 );
    if( tp_fd != NULL )
    {
        T_threadPoolAddTask( tp_fd , test_0 , NULL , 10 );
        T_threadPoolAddTask( tp_fd , test_1 , NULL , 10 );
        T_threadPoolAddTask( tp_fd , test_2 , NULL , 10 );
    }
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    T_threadPoolDelete( tp_fd );
    printf("结束\r\n");
    return 0;
}
*/

#elif 0

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/message_buffer.h" //使用消息缓冲区必须要有这个头文件
typedef struct
{
    char *name;
    int age;
} info_t;

//===============================Task实现函数，不同四种参数的传入============================
void My_task1(void *param)
{
    int *data;
    data = (int *)param;
    while (1) // 死循环
    {
        printf("Running Task1! param = %d\n", *data);
        vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时一段时间
    }
}
void My_task2(void *param)
{
    int *data;
    data = (int *)param;
    printf("Running Task2!param = {%d, %d, %d}\n", *data, *(data + 1), *(data + 2));
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时一段时间
    vTaskDelete(NULL);
}
void My_task3(void *param)
{
    char *data;
    data = (char *)param;
    while (1) // 死循环
    {
        printf("Running Task3!param = %s\n", data);
        vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时一段时间
    }
}

void My_task4(void *param)
{
    info_t *data;
    data = (info_t *)param;
    while (1) // 死循环
    {
        printf("Running Task4!param ={name:%s, age:%d}\n", data->name, data->age);
        vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时一段时间
    }
}
/***************静态分配自旋锁并使用 portMUX_INITIALIZER_UNLOCKED 初始化：******************/
// 静态分配并初始化自旋锁
static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED;

void some_function(void)
{
    taskENTER_CRITICAL(&my_spinlock);
    // 此时已处于临界区
    taskEXIT_CRITICAL(&my_spinlock);
}
/***************静态分配自旋锁并使用 portMUX_INITIALIZER_UNLOCKED 初始化：******************/

//========================================程序入口===========================================
void app_main(void)
{

    /*************静态分配自旋锁并使用 portMUX_INITIALIZE() 初始化：****************/
    // 动态分配自旋锁
    portMUX_TYPE *my_spinlock = malloc(sizeof(portMUX_TYPE));
    // 动态初始化自旋锁
    portMUX_INITIALIZE(my_spinlock);
    taskENTER_CRITICAL(my_spinlock);
    // 访问资源
    taskEXIT_CRITICAL(my_spinlock);
    /*************静态分配自旋锁并使用 portMUX_INITIALIZE() 初始化：****************/

    // Task句柄，用于之后相应操作，假如不操作可以产生
    TaskHandle_t task_handle3;

    int param1 = 5;
    int param2[3] = {1, 2, 3};
    char *param3 = "Hello FreeRTOS!";
    info_t param4 = {
        .name = "LiHua",
        .age = 15};
    // 创建任务并与实现函数关联，分配堆栈空间(Byte)，传入参数，设定优先级0-24，传入句柄
    xTaskCreate(My_task1, "Task1", 2048, (void *)&param1, 10, NULL);
    xTaskCreate(My_task2, "Task2", 2048, (void *)param2, 10, NULL);
    xTaskCreate(My_task3, "Task3", 2048, (void *)param3, 10, &task_handle3);
    xTaskCreate(My_task4, "Task4", 2048, (void *)&param4, 10, NULL);

    vTaskDelay(5000 / portTICK_PERIOD_MS);
    // 任务删除，可以在任务内部删除，也可以在主程序中
    if (task_handle3 != NULL)
    {
        vTaskDelete(task_handle3);
        printf("shanchu -- Task3\n");
    }
    for (int i = 20; i >= 0; i--)
    {
        printf("Restarting in %d seconds...\n", i);
        if (i < 2)
        {
            i = 20;
        }

        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

#elif 0

#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/message_buffer.h" //使用消息缓冲区必须要有这个头文件
#include "freertos/event_groups.h"   //xEventGroupCreate(void)
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_chip_info.h"

// 定义消息缓冲区句柄
MessageBufferHandle_t xMessageBuffer;
const size_t xMessageBufferSizeBytes = 100;

// 创建队列并发布值的任务
void vATask(void *pvParameters)
{
    size_t xBytesSent;
    uint8_t ucArrayToSend[] = {'0', '1', '2', '3', 0};
    char *pcStringToSend = "String to send";
    const TickType_t x100ms = pdMS_TO_TICKS(100);
    // 创建消息缓冲区
    xMessageBuffer = xMessageBufferCreate(xMessageBufferSizeBytes);
    if (xMessageBuffer == NULL)
    {
        printf("message buffer Create fail!\n");
    }
    else
    {
        printf("message buffer Create successed!\n");
    }

    while (1)
    { // 消息缓存区发送的第一种方式
        // 将数组发送到消息缓冲区，最多阻塞 100 毫秒，以等待消息缓冲区中有足够的可用空间。
        xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)ucArrayToSend, sizeof(ucArrayToSend), x100ms);

        if (xBytesSent != sizeof(ucArrayToSend))
        {
            // 在缓冲区中有足够的空间用于写入数据之前，对 xMessageBufferSend() 的调用超时。
        }

        // 消息缓存区发送的第二种方式
        //  将字符串发送到消息缓冲区。如果缓冲区空间不足，则立即返回。strlen 计算字符串长度不含结尾"\0"
        xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)pcStringToSend, strlen(pcStringToSend), 0);
        if (xBytesSent != strlen(pcStringToSend))
        {
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

// 从队列接收的任务。
void vADifferentTask(void *pvParameters)
{
    while (1)
    {
        uint8_t ucRxData[20];
        size_t xReceivedBytes;
        const TickType_t xBlockTime = pdMS_TO_TICKS(20);
        // 接收消息
        // 从消息缓冲区接收下一条消息。在阻塞状态下等待（因此不使用任何 CPU 处理时间）最多 100 毫秒，消息才可用。
        xReceivedBytes = xMessageBufferReceive(xMessageBuffer,
                                               (void *)ucRxData,
                                               sizeof(ucRxData),
                                               xBlockTime);

        if (xReceivedBytes > 0)
        {
            printf(" MessageBuffer: ucRxData is %d long. ucRxData : %s \n", xReceivedBytes, ucRxData);
            // 这里的消息......
        }
    }
}
void app_main(void)
{
    EventGroupHandle_t xEventGroup1;
    EventGroupHandle_t xEventGroup2;
    /* 打印芯片信息 */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    xTaskCreate(vATask, "vATask", 1024 * 2, NULL, configMAX_PRIORITIES - 3, NULL);
    xTaskCreate(vADifferentTask, "vADifferentTask", 1024 * 2, NULL, configMAX_PRIORITIES - 4, NULL);
    /*
    xEventGroup1 =xEventGroupCreate();//创建新的事件组
    xEventGroup2 =xEventGroupCreate();
    if (xEventGroup1 == NULL)
    {
        printf("创建xEventGroup1失败\r\n");
    }else
    {
        printf("创建xEventGroup1成功\r\n");
    }
    if (xEventGroup2 == NULL)
    {
        printf("创建xEventGroup2失败\r\n");
    }else
    {
        printf("创建xEventGroup2成功\r\n");
    }
    */
    while (1)
    {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

#elif 0
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/timer.h"
#include "esp_timer.h"
#include "driver/uart.h"

#define TXD1_PIN (GPIO_NUM_17) // 串口1的发送数据引脚
#define RXD1_PIN (GPIO_NUM_16) // 串口1的接收数据引脚
#define BUF_SIZE (1024)        // 接收数据缓存大小,该大小需要大于内部FIFO大小:UART_FIFO_LEN(128)

#define BUF_SEND_SIZE (1024) // 发送数据缓存大小,该大小需要大于内部FIFO大小:UART_FIFO_LEN(128)

static QueueHandle_t QueueHandle_t_uart1;

void Uart_DataAnalysis(uint8_t *str, int size);
int strfindsub(const char *haystack, char ch);
char *mysubstr(char *srcstr, int offset, int length);
/*串口任务*/
static void uart_task(void *arg)
{
    /*配置串口参数*/
    uart_config_t uart_config = {
        .baud_rate = 115200,                   // 波特率
        .data_bits = UART_DATA_8_BITS,         // 数据位8位
        .parity = UART_PARITY_DISABLE,         // 无奇偶校验
        .stop_bits = UART_STOP_BITS_1,         // 停止位1位
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, // 不使用硬件流控
        .source_clk = UART_SCLK_APB,           // 串口使用的时钟
    };
    /*初始化串口1*/
    uart_driver_install(UART_NUM_1,
                        BUF_SIZE,             // 串口1接收缓存大小
                        BUF_SEND_SIZE,        // 串口1发送缓存大小
                        10,                   // 队列大小为10
                        &QueueHandle_t_uart1, // 缓存管理
                        0                     // 设置串口中断优先级,设置为0意味着让系统从1-3级中自动选择一个
    );
    /*设置串口参数*/
    uart_param_config(UART_NUM_1, &uart_config);
    /*设置串口的TX,RX,RTS,DTR引脚*/ // 不使用RTS,DTR
    uart_set_pin(UART_NUM_1, TXD1_PIN, RXD1_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    // 设置串口模式 485半双工通讯模式
    // ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));
    // 设置UART TOUT功能的读取超时
    // ESP_ERROR_CHECK(uart_set_rx_timeout(uart_num, ECHO_READ_TOUT));
    /*申请一块内存,用于临时存储接收的数据*/
    uint8_t *data = (uint8_t *)malloc(BUF_SIZE);
    uart_event_t event;
    while (1)
    {
        if (xQueueReceive(QueueHandle_t_uart1, (void *)&event, portMAX_DELAY))
        {
            switch (event.type)
            {
            case UART_DATA: // 接收到数据
                // 读取接收的数据
                uart_read_bytes(UART_NUM_1, data, event.size, portMAX_DELAY);
                Uart_DataAnalysis(data, event.size);
                // 返回接收的数据
                uart_write_bytes(UART_NUM_1, (const char *)data, event.size);
                break;
            case UART_FIFO_OVF: // FIFO溢出(建议加上数据流控制)
                uart_flush_input(UART_NUM_1);
                xQueueReset(QueueHandle_t_uart1);
                break;
            case UART_BUFFER_FULL: // 接收缓存满(建议加大缓存 BUF_SIZE)
                uart_flush_input(UART_NUM_1);
                xQueueReset(QueueHandle_t_uart1);
                break;
            case UART_BREAK: // 检测到接收数据中断
                break;
            case UART_PARITY_ERR: // 数据校验错误
                break;
            case UART_FRAME_ERR: // 数据帧错误
                break;
            case UART_PATTERN_DET: // 接收到相匹配的字符(没用到)
                break;
            default:
                break;
            }
        }
    }
    free(data);
    data = NULL;
    vTaskDelete(NULL);
}
// 串口事件信息分析处理
void Uart_DataAnalysis(uint8_t *str, int size)
{
    char *rest = NULL;
    int mark = 0;
    char *value = NULL;
    char scmd[2] = {0};
    char delim = ':';
    char *stemp = (char *)str;

    printf("read usrt:%s\n", stemp);

    rest = strncpy(scmd, stemp, 1); // 获取命令
    if (rest == NULL)
    {
        printf("uart input scmd format is error.\n");
        return;
    }
    mark = strfindsub(stemp, delim);
    value = mysubstr(stemp, mark + 1, (size - mark - 1)); // 获取命令携带的信息
    int cmd = atoi(scmd);
    /*
    switch (cmd)
    {
        case FCT_CMD_WRITESN_FCT:         // 写入SN
            printf("FCT write sn read value:%s\n", (char *)value);
            break;
        case FCT_CMD_REQUEST_FCT: // 请求进入FCT
            printf("FCT start read value:%s\n", (char *)value);
            break;
        case FCT_CMD_SET_NET_STAR: // 开始配网

            break;
        case FCT_CMD_SET_NET_STEP: // 断开网络

            break;
        case FCT_CMD_SET_WIFI_INFO: // 获取wifi信息

            break;
        default:
            break;
    }
    */
}
// 获取字符串中固定字符第一次出现的位置
int strfindsub(const char *haystack, char ch)
{
    int cnt = 0;
    while (haystack[cnt] != ch)
    {
        cnt++;
    }

    return cnt;
}
// 拷贝字符串指定位置间的子字符串
char *mysubstr(char *srcstr, int offset, int length)
{
    assert(length > 0);
    assert(srcstr != NULL);

    int total_length = strlen(srcstr); // 首先获取srcstr的长度
    // 判断srcstr的长度减去需要截取的substr开始位置之后，剩下的长度
    // 是否大于指定的长度length，如果大于，就可以取长度为length的子串
    // 否则就把从开始位置剩下的字符串全部返回。
    int real_length = ((total_length - offset) >= length ? length : (total_length - offset)) + 1;
    char *tmp;
    if (NULL == (tmp = (char *)malloc(real_length * sizeof(char))))
    {
        printf("Memory overflow . \n");
        exit(0);
    }
    strncpy(tmp, srcstr + offset, real_length - 1);
    tmp[real_length - 1] = '\0';

    return tmp;
}
void app_main(void)
{
    xTaskCreate(uart_task, "uart_task", 2048, NULL, 10, NULL);
    while (1)
    {
        vTaskDelay(10 / portTICK_PERIOD_MS); // 定时器延时
    }
}

#elif 0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"
struct AMessage
{
    char ucMessageID;
    char ucData[20];
} xMessage;

QueueHandle_t xQueue;

// Task to create a queue and post a value.
void vATask(void *pvParameters)
{
    struct AMessage *pxMessage;

    // Create a queue capable of containing 10 pointers to AMessage structures.
    // These should be passed by pointer as they contain a lot of data.
    xQueue = xQueueCreate(10, sizeof(struct AMessage *));
    if (xQueue == 0)
    {
        // Failed to create the queue.
    }

    // ...

    // Send a pointer to a struct AMessage object.  Don't block if the
    // queue is already full.
    pxMessage = &xMessage;
    xQueueSend(xQueue, (void *)&pxMessage, (TickType_t)0);

    // ... Rest of task code.
}

// Task to receive from the queue.
void vADifferentTask(void *pvParameters)
{
    struct AMessage *pxRxedMessage;

    if (xQueue != 0)
    {
        // Receive a message on the created queue.  Block for 10 ticks if a
        // message is not immediately available.
        if (xQueueReceive(xQueue, &(pxRxedMessage), (TickType_t)10))
        {
            // pcRxedMessage now points to the struct AMessage variable posted
            // by vATask.
        }
    }

    // ... Rest of task code.
}

#elif 0

/*********************************************************************
 * INCLUDES
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "esp_log.h"

#define BUF_SIZE (1024)
#define UART_MAX_NUM_RX_BYTES (1024)

static void uartEventTask(void *pvParameters);

/*********************************************************************
 * LOCAL VARIABLES
 */
static QueueHandle_t s_uart0Queue;

static const char *TAG = "board_uart";

/*********************************************************************
 * PUBLIC FUNCTIONS
 */
/**
 @brief 串口驱动初始化
 @param 无
 @return 无
*/
void UART_Init(void)
{
    // Configure parameters of an UART driver,
    // communication pins and install the driver
    uart_config_t uart_config =
        {
            .baud_rate = 115200,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE};

    uart_param_config(UART_NUM_0, &uart_config);                                                              // 配置串口0参数
    uart_set_pin(UART_NUM_0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); // 配置串口0引脚
    // Install UART driver, and get the queue.
    uart_driver_install(UART_NUM_0, BUF_SIZE * 2, BUF_SIZE * 2, 100, &s_uart0Queue, 0); // 安装UART驱动程序

    // Create a task to handler UART event from ISR
    xTaskCreate(uartEventTask, "uartEventTask", 2048, NULL, 12, NULL);
}

/*********************************************************************
 * LOCAL FUNCTIONS
 */
static void uartEventTask(void *pvParameters)
{
    uart_event_t event;
    uint8_t *pTempBuf = (uint8_t *)malloc(UART_MAX_NUM_RX_BYTES);

    for (;;)
    {
        // Waiting for UART event.
        if (xQueueReceive(s_uart0Queue, (void *)&event, (TickType_t)10)) // portTICK_PERIOD_MS,( TickType_t ) 10,(portTickType)portMAX_DELAY)
        {
            bzero(pTempBuf, UART_MAX_NUM_RX_BYTES);

            switch (event.type)
            {
            // Event of UART receving data
            // We'd better handler data event fast, there would be much more data events than
            // other types of events. If we take too much time on data event, the queue might be full.
            case UART_DATA:
                // ESP_LOGI(TAG, "[UART DATA]: %d", event.size);
                uart_read_bytes(UART_NUM_0, pTempBuf, event.size, portMAX_DELAY);
                uart_write_bytes(UART_NUM_0, (const char *)pTempBuf, event.size);
                break;

            // Event of HW FIFO overflow detected
            case UART_FIFO_OVF:
                ESP_LOGI(TAG, "hw fifo overflow");
                // If fifo overflow happened, you should consider adding flow control for your application.
                // The ISR has already reset the rx FIFO,
                // As an example, we directly flush the rx buffer here in order to read more data.
                uart_flush_input(UART_NUM_0);
                xQueueReset(s_uart0Queue);
                break;

            // Event of UART ring buffer full
            case UART_BUFFER_FULL:
                ESP_LOGI(TAG, "ring buffer full");
                // If buffer full happened, you should consider encreasing your buffer size
                // As an example, we directly flush the rx buffer here in order to read more data.
                uart_flush_input(UART_NUM_0);
                xQueueReset(s_uart0Queue);
                break;

            case UART_PARITY_ERR:
                ESP_LOGI(TAG, "uart parity error");
                break;

            // Event of UART frame error
            case UART_FRAME_ERR:
                ESP_LOGI(TAG, "uart frame error");
                break;

            // Others
            default:
                ESP_LOGI(TAG, "uart event type: %d", event.type);
                break;
            }
        }
    }

    free(pTempBuf);
    pTempBuf = NULL;
    vTaskDelete(NULL);
}

void app_main(void)
{
    UART_Init();
}

#elif 0

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "esp_err.h"

#define UART1_TX_BUF_SIZE 134 /* 256个字节 */
#define UART1_RX_BUF_SIZE 134 /* 256个字节 */
#define UART1_RX GPIO_NUM_16  /* 256个字节 */
#define UART1_TX GPIO_NUM_17  /* 256个字节 */

static const char *TAG = "UART section0";

static char msg_test[] = "hello,world"; /* 测试使用的字符串 */
static char buffer[UART1_RX_BUF_SIZE];  /* 暂时存储从串口接收到的字符串 */

/* 串口1的配置 */
const uart_config_t uart1_config =
    {
        .baud_rate = 115200,                   /* 通讯波特率 */
        .data_bits = UART_DATA_8_BITS,         /* 每一个数据是8位 */
        .parity = UART_PARITY_DISABLE,         /* 关闭奇偶校验 */
        .stop_bits = UART_STOP_BITS_1,         /* 停止位是1位 */
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, /* 软件控流 */
        .source_clk = UART_SCLK_APB,           /* APB时钟 */
};

void app_main(void)
{
    ESP_LOGI(TAG, "Example configured to uart communication");

    /* 设置串口1的参数 */
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &uart1_config));

    /* 设置串口的gpio口，esp32支持gpio口动态设置，这一次先使用默认的串口gpio */
    ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, UART1_TX, UART1_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));

    /* 启动串口1 */
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1,        /* 串口1 */
                                        UART1_TX_BUF_SIZE, /* 发送FIFO的大小 */
                                        UART1_RX_BUF_SIZE, /* 接受FIFO的大小 */
                                        0,                 /* 不使用queue */
                                        NULL,              /* 因为不使用queue，所以NULL */
                                        0)                 /* 不分配中断标志 */
    );

    while (1)
    {
        // uart_write_bytes(UART_NUM_1, msg_test, strlen(msg_test)); /* 将字符串“hello,world"放入串口1的TX_FIFO */

        // uart_write_bytes(UART_NUM_0,msg_test1,strlen(msg_test1));  /* 将字符串“hello,world"放入串口1的TX_FIFO */

        vTaskDelay(500 / portTICK_PERIOD_MS); /* 延时300ms */

        int len = uart_read_bytes(UART_NUM_1, buffer, (UART1_RX_BUF_SIZE - 1), 20 / portTICK_PERIOD_MS); /* 从串口1的RX_FIFO获取字符串 */

        // int len1 = uart_read_bytes(UART_NUM_0, buffer1, (UART1_RX_BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
        /* 如果读到包的话 */
        if (len)
        {
            buffer[len] = '\0';                                                                        /* 在结尾加入字符'\0'， */
            ESP_LOGI(TAG, "Recv str -> %s , and the length is:%d", buffer, strlen(buffer));            /* 打印logo */
            ESP_LOGI(TAG, "The size of buffer is %d,and ready to clear this buffer.", sizeof(buffer)); /* 打印logo */
            uart_write_bytes(UART_NUM_1, buffer, strlen(buffer));
            memset(buffer, 0, sizeof(buffer)); /* 清空内存，等待下一次的串口保文。 */
        }
    }
}

#elif 0 // 串口
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h" //sdk配置
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"           //任务
#include "freertos/message_buffer.h" //使用消息缓冲区必须要有这个头文件
#include "esp_chip_info.h"           //芯片_信息
#include "esp_flash.h"               //内存
#include "esp_system.h"              //系统
#include "esp_spi_flash.h"
#include "driver/uart.h"            //串口
#include "esp_log.h"                //日志
#include "gatts_table_creat_demo.h" //ble蓝牙
#include "driver/gpio.h"            //GPIO
#include "driver/ledc.h"            //LEDPWM
#include "driver/mcpwm.h"           //电机LEDPWM

static const char *TAG = "example1";
/*串口*/
#define UART1_TX_BUF_SIZE 132       /* 256个字节 */
#define UART1_RX_BUF_SIZE 132       /* 256个字节 */
#define UART1_RX GPIO_NUM_16        /* 256个字节 */
#define UART1_TX GPIO_NUM_17        /* 256个字节 */
static char msg_test[] = "hello,world"; /* 测试使用的字符串 */
static char buffer[UART1_RX_BUF_SIZE];  /* 暂时存储从串口接收到的字符串 */

/*串口初始化*/
void uartInit()
{
    /* 串口1的配置 */
    const uart_config_t uart1_config =
        {
            .baud_rate = 115200,                   /* 通讯波特率 */
            .data_bits = UART_DATA_8_BITS,         /* 每一个数据是8位 */
            .parity = UART_PARITY_DISABLE,         /* 关闭奇偶校验 */
            .stop_bits = UART_STOP_BITS_1,         /* 停止位是1位 */
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, /* 软件控流 */
            .source_clk = UART_SCLK_APB,           /* APB时钟 */
        };
    /* 设置串口1的参数 */
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_1,      /*UART 端口号，最大端口号为(UART_NUM_MAX -1)。*/
                                      &uart1_config)); /*UART 参数设置*/

    /* 设置串口的gpio口，esp32支持gpio口动态设置，这一次先使用默认的串口gpio */
    ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1,           /*UART 端口号*/
                                 UART1_TX,             /*UART TX 引脚 GPIO 编号*/
                                 UART1_RX,             /*UART RX 引脚 GPIO 编号*/
                                 UART_PIN_NO_CHANGE,   /*UART RTS 引脚 GPIO 编号*/
                                 UART_PIN_NO_CHANGE)); /*CTS 引脚 GPIO 编号*/

    /* 启动串口1 */
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1,        /* 串口1 */
                                        UART1_TX_BUF_SIZE, /* 发送FIFO的大小 */
                                        UART1_RX_BUF_SIZE, /* 接受FIFO的大小 */
                                        0,                 /* 不使用queue */
                                        NULL,              /* 因为不使用queue，所以NULL */
                                        0)                 /* 不分配中断标志 */
    );
}

/*接收数据*/
void uartH()
{
    const uart_port_t uart_num = UART_NUM_1;
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS,
        .rx_flow_ctrl_thresh = 122,
    };
    // 配置串口参数
    ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
    int len = 0;
    while (1)
    {
        // UART 从 UART 缓冲区读取字节,从串口1的RX_FIFO获取字符串
        len = uart_read_bytes(UART_NUM_1,               // UART端口号
                              buffer,                   // 指向缓冲区的指针
                              (UART1_RX_BUF_SIZE - 1),  // 数据长度
                              20 / portTICK_PERIOD_MS); // 等待时间
        // 如果读到包的话
        if (len)
        {
            printf("len  =======   %d\n", len);
            buffer[len] = '\0';                                                             // 在结尾加入字符'\0'，
            ESP_LOGI(TAG, "Recv str -> %s , and the length is:%d", buffer, strlen(buffer)); // 打印logo
            // uart_write_bytes(UART_NUM_1, buffer, strlen(buffer));//发送数据到串口1
            memset(buffer, 0, sizeof(buffer)); // 清空内存，等待下一次的串口报文。
        }
    }
}

void app_main(void)
{
    uartInit();
    void uartH();
}

#elif 0 // 中断,二进制信号量

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h" //二进制信号量xSemaphoreCreateBinary()
#include "driver/gpio.h"
#include "esp_log.h"

#define TAG "app"

#define ESP_INTR_FLAG_DEFAULT 0

static SemaphoreHandle_t gpio_sem;

#define GPIO_DEMO_PIN GPIO_NUM_18

static void IRAM_ATTR gpio_isr_handler(void *arg)
{
    xSemaphoreGiveFromISR(gpio_sem, NULL);
}

static void gpio_task_example(void *arg)
{
    for (;;)
    {
        if (xSemaphoreTake(gpio_sem, portMAX_DELAY))
        {
            ESP_LOGI(TAG, "GPIO[%d] intr, val: %d\n", GPIO_DEMO_PIN, gpio_get_level(GPIO_DEMO_PIN));
        }
    }
}

void app_main(void)
{
    // install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);

    // zero-initialize the config structure.
    gpio_config_t io_conf = {};

    // interrupt of rising edge
    io_conf.intr_type = GPIO_INTR_POSEDGE;
    io_conf.pin_bit_mask = 1ULL << GPIO_DEMO_PIN;
    // set as input mode
    io_conf.mode = GPIO_MODE_INPUT;
    // enable pull-up mode
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);

    // 二进制信号量
    gpio_sem = xSemaphoreCreateBinary();
    // start gpio task
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);

    gpio_isr_handler_add(GPIO_DEMO_PIN, gpio_isr_handler, NULL);

    // 如果某个时刻不想产生中断可以将其从中断服务中删除
    // gpio_isr_handler_remove(GPIO_DEMO_PIN);
}
#elif 0 // 中断,2个IO口
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/gpio.h>

#define FIRST_INTERRUPT_PIN GPIO_NUM_2
#define SECOND_INTERRUPT_PIN GPIO_NUM_4

// 用于第一个GPIO的配置和引脚选择
static gpio_num_t gpio_num1 = FIRST_INTERRUPT_PIN;
static gpio_pad_select_gpio_t gpio_pad1 = {.gpio_num = FIRST_INTERRUPT_PIN};
static const gpio_config_t gpio_config1 = {
    .pin_bit_mask = 1 << gpio_num1,
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = 0,
    .pull_down_en = 0,
    .intr_type = GPIO_INTR_ANYEDGE,
};

// 用于第二个GPIO的配置和引脚选择
static gpio_num_t gpio_num2 = SECOND_INTERRUPT_PIN;
static gpio_pad_select_gpio_t gpio_pad2 = {.gpio_num = SECOND_INTERRUPT_PIN};
static const gpio_config_t gpio_config2 = {
    .pin_bit_mask = 1 << gpio_num2,
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = 0,
    .pull_down_en = 0,
    .intr_type = GPIO_INTR_ANYEDGE,
};

void IRAM_ATTR gpio_isr_handler(void *arg)
{
    uint32_t gpio_num = (uint32_t)arg;

    if (gpio_num == gpio_num1)
    {
        // 第一个GPIO引脚的中断处理逻辑
        // ...
        printf("Interrupt on GPIO %d\n", gpio_num);
    }
    else if (gpio_num == gpio_num2)
    {
        // 第二个GPIO引脚的中断处理逻辑
        // ...
        printf("Interrupt on GPIO %d\n", gpio_num);
    }
}

void app_main()
{
    // 初始化第一个GPIO
    gpio_pad_select_gpio(gpio_pad1);
    gpio_configure(gpio_num1, &gpio_config1);
    gpio_isr_handler_add(gpio_num1, gpio_isr_handler, (void *)gpio_num1);

    // 初始化第二个GPIO
    gpio_pad_select_gpio(gpio_pad2);
    gpio_configure(gpio_num2, &gpio_config2);
    gpio_isr_handler_add(gpio_num2, gpio_isr_handler, (void *)gpio_num2);

    // 安装GPIO中断服务例程
    gpio_install_isr_service();
}

#elif 0 // 中断,消息传递

#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h" //二进制信号量xSemaphoreCreateBinary()

#define LED GPIO_NUM_2
#define ZHONG_DUAN_1 GPIO_NUM_17

// 消息队列句柄
static QueueHandle_t s_queue_led = NULL;

static QueueHandle_t gpio_sem;

// 中断处理函数
static void IRAM_ATTR ledIsr(void *arg)
{
    // 向队列写true或false,虽然变量传的是地址，实际上是值传递
    if (xQueueSendFromISR(s_queue_led, (bool *)arg, NULL) != pdPASS)
        return;
    *(bool *)arg = !*(bool *)arg; // 真假翻转
}

// 与main并发的任务
void task_led(void *arg)
{
    // 配置GPIO2输出，此脚连接led
    gpio_set_direction(LED, GPIO_MODE_OUTPUT);
    bool led_flag = true;
    int ret = 0;

    printf("led005\r\n");
    while (true)
    {
        // 非阻塞读，读错或队列空不影响led闪烁
        ret = xQueueReceive(s_queue_led, &led_flag, 0);

        if (led_flag)
        {
            printf("led正在闪烁...%d\n", led_flag);
            gpio_set_level(LED, 1);
            vTaskDelay(500 / portTICK_PERIOD_MS);
            gpio_set_level(LED, 0);
        }
        else if (ret == pdPASS)
        {
            printf("led停止闪烁%d\n", led_flag);
            gpio_set_level(LED, 0);
        }

        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
}

// 单个IO口中断初始化,参数1:IO口编号,参数2,teue为高电平上沿
bool zhong_duan_init(uint64_t ioKou, bool shangXia)
{
    if (shangXia)
    {
        if (gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1) != ESP_OK)
        {
            printf("led中断服务错误,函数返回\r\n");
            return false;
        }
        gpio_reset_pin(ZHONG_DUAN_1);
        gpio_config_t io_t = {};
        io_t.intr_type = GPIO_INTR_POSEDGE;
        io_t.pin_bit_mask = 1ULL << ZHONG_DUAN_1;
        io_t.mode = GPIO_MODE_INPUT;
        io_t.pull_down_en = GPIO_PULLDOWN_ENABLE;
        gpio_config(&io_t);
        return true;
    }
    else
    {
        if (gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1) != ESP_OK)
        {
            printf("led中断服务错误,函数返回\r\n");
            return false;
        }
        gpio_reset_pin(ZHONG_DUAN_1);
        gpio_config_t io_t = {};
        io_t.intr_type = GPIO_INTR_POSEDGE;
        io_t.pin_bit_mask = 1ULL << ZHONG_DUAN_1;
        io_t.mode = GPIO_MODE_INPUT;
        io_t.pull_up_en = GPIO_PULLUP_ENABLE;
        gpio_config(&io_t);
        return true;
    }
}

void app_main(void)
{
    gpio_reset_pin(LED); // 清除GPIO状态
    // 配置GPIO18下降沿中断并指定中断函数
    if (false == zhong_duan_init(17, true))
    {
        printf("中断服务错误,请检查...\r\n");
        return;
    }

    printf("led001\r\n");
    // 创建消息队列
    s_queue_led = xQueueCreate(1, sizeof(bool));
    // 创建二进制信号量
    gpio_sem = xSemaphoreCreateBinary();

    if (s_queue_led == NULL)
    {
        printf("消息队列创建失败,函数返回\r\n");
        return;
    }
    // 创建任务
    if (xTaskCreate(task_led, "led", 2048, NULL, 10, NULL) == pdFALSE)
    {
        printf("task_led创建失败,函数返回\r\n");
        return;
    }
    printf("led001-1\r\n");

    /*中断处理函数*/
    static bool s_isr_flag = true;
    gpio_isr_handler_add(ZHONG_DUAN_1, ledIsr, &s_isr_flag);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    printf("led002\r\n");

    while (true)
    {
        printf("只因你太美\n");
        vTaskDelay(4500 / portTICK_PERIOD_MS);
    }
}

#elif 0

/* WiFi station Example
   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sys.h"

/* 这些示例使用 WiFi 配置，您可以通过项目配置菜单进行设置。如果您不愿意，只需将以下条目更改为具有您想要的配置的字符串 -
即 #define Examples_WIFI_SSID &quot;mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID "cccxxx"
#define EXAMPLE_ESP_WIFI_PASS "chenxin1234567890"
#define EXAMPLE_ESP_MAXIMUM_RETRY 5

/* FreeRTOS 事件组在我们连接时发出信号*/
static EventGroupHandle_t s_wifi_event_group;

/* 事件组允许每个事件有多个位，但我们只关心两个事件：
 * -我们已通过 IP 连接到 AP
 * - 在达到最大重试次数后，我们无法连接 */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1

static const char *TAG = "wifi station";

static int s_retry_num = 0;

static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        esp_wifi_connect();
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
        {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        }
        else
        {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG, "connect to the AP fail");
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());

    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,

            .pmf_cfg = {
                .capable = true,
                .required = false},
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                                           WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                           pdFALSE,
                                           pdFALSE,
                                           portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT)
    {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    }
    else if (bits & WIFI_FAIL_BIT)
    {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    }
    else
    {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }

    /* The event will not be processed after unregister */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);
}

void app_main(void)
{
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();
}

#elif 0 // WIFI连接

#include <nvs_flash.h>
#include <esp_event.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <esp_netif.h>
#include <esp_netif_ip_addr.h>

#include <stdio.h>
#include <lwip/netif.h>

static const char *TAG = "WIFI: ";

/**
 * @brief 用于初始化nvs
 */
void init_nvs()
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);
}

/**
 * @brief WiFi 的事件循环Handler
 * @param arg
 * @param event_base
 * @param event_id
 * @param event_data
 */
void wifi_event_handler(void *arg,                   // 这是一个指向任意类型的指针，通常用于传递给事件处理器的自定义参数
                        esp_event_base_t event_base, // 这个参数表示事件来自哪个事件源
                        int32_t event_id,            // 这个参数表示事件的ID
                        void *event_data             // 这个参数通常是一个指向特定数据结构的指针
)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        esp_wifi_connect();
    }

    if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI("TEST_ESP32", "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        // ESP_LOGI("TEST_ESP32", "IP: %d.%d.%d.%d", (&event->ip_info.ip));
    }
}

void app_main(void)
{
    init_nvs();

    esp_netif_init();

    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    //  注：下方的cfg_sta也可以写成这样
    //  wifi_config_t wifi_config = {
    //      .sta = {
    //          .ssid = "SSID",
    //          .password = "密码",
    //      }
    //  };
    wifi_sta_config_t cfg_sta = {
        .ssid = "cccxxx",
        .password = "chenxin1234567890",
    };
    //  而直接将wifi_sta_config_t(或指针)转为wifi_config_t(或指针)是GCC的拓展语法，如下
    esp_wifi_set_config(WIFI_IF_STA, (wifi_config_t *)&cfg_sta);

    esp_wifi_set_mode(WIFI_MODE_STA);

    esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL, NULL);
    esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL, NULL);

    esp_wifi_start();
}

#elif 0 // 成功连接,ESP32S模块不能连接

#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#define LIGHT_ESP_WIFI_SSID "cccxxx"
#define LIGHT_ESP_WIFI_PASS "chenxin1234567890"
#define LIGHT_ESP_MAXIMUM_RETRY 500

// 事件组允许每个事件有多个位，这里只使用两个事件：
// 已经连接到AP并获得了IP
// 在最大重试次数后仍未连接
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1

static const char *TAG = "wifi: ";

// FreeRTOS事件组，连接成功时发出信号
static EventGroupHandle_t s_wifi_event_group = NULL;
static int s_retry_num = 0;

// 事件回调
static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    // 如果是Wi-Fi事件，并且事件ID是Wi-Fi事件STA_START
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        esp_wifi_connect();
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        // 如果是Wi-Fi事件，并且事件ID是Wi-Fi断开事件
        /* 如果重试次数小于最大重试次数 */
        if (s_retry_num < LIGHT_ESP_MAXIMUM_RETRY)
        {
            esp_wifi_connect();
            if (s_retry_num > 200)
            {
                vTaskDelay(pdMS_TO_TICKS(60000));
                if (s_retry_num > 400)
                {
                    s_retry_num = 0;
                }
            }
            s_retry_num++;
            ESP_LOGI(TAG, "重试连接AP\n");
            vTaskDelay(pdMS_TO_TICKS(6000));
        }
        else
        {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG, "连接AP失败\r\n");
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        // 如果是IP事件，并且事件ID是IP事件STA_GOT_IP

        // 获取事件结果
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));

        // 将重试次数重置为 0；
        s_retry_num = 0;
        // 通过调用 xEventGroupSetBits 函数，将 WIFI_CONNECTED_BIT 设置到事件组中，表示成功连接到 AP
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

static void wifi_initialize(void)
{
    // 创建一个事件组，用于管理Wi-Fi连接事件。
    s_wifi_event_group = xEventGroupCreate();

    // 初始化 TCP/IP 协议栈。
    ESP_ERROR_CHECK(esp_netif_init());

    // 创建默认事件循环。
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 创建默认的Wi-Fi网络接口。
    esp_netif_create_default_wifi_sta();
    // 设置 Wi-Fi 初始化配置为默认配置
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 注册事件处理器，以处理 Wi-Fi 和 IP 相关事件
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
}

static void wifi_station_initialize(void)
{
    // 配置WiFi的配置信息
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = LIGHT_ESP_WIFI_SSID,
            .password = LIGHT_ESP_WIFI_PASS,
            // 启用WPA2模式，常用的WiFi连接方式  WIFI_AUTH_WPA2_PSK
            .threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,

            .pmf_cfg = {
                .capable = true,
                .required = false},
        },
    };
    // WiFi工作模式设置为STA
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    // 设置WiFi工作模式
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    // 启动WiFi
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_station_initialize finished.");

    /* 等待连接建立（WIFI_CONNECTED_BIT）或连接失败的次数达到最大值（WIFI_FAIL_BIT）。
     * 这些位通过 event_handler() 设置（详见上面）*/
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    /* xEventGroupWaitBits() 返回调用前的 bits，因此我们可以测试实际发生了什么事件。 */
    if (bits & WIFI_CONNECTED_BIT)
    {
        ESP_LOGI(TAG, "连接到 ap SSID:%s password:%s\n", LIGHT_ESP_WIFI_SSID, LIGHT_ESP_WIFI_PASS);
    }
    else if (bits & WIFI_FAIL_BIT)
    {
        ESP_LOGI(TAG, "无法连接到 SSID:%s, password:%s\n", LIGHT_ESP_WIFI_SSID, LIGHT_ESP_WIFI_PASS);
    }
    else
    {
        ESP_LOGE(TAG, "意外事件\n");
    }
}

void app_main()
{
    int i = 0;
    ESP_LOGE(TAG, "app_main");
    // 初始化NVS存储区
    ESP_ERROR_CHECK(nvs_flash_init());

    // Wi-Fi初始化
    ESP_LOGI(TAG, "Wi-Fi initialization");
    wifi_initialize();

    // Wi-Fi Station初始化
    wifi_station_initialize();

    while (1)
    {
        ESP_LOGI(TAG, "[%02d] Hello world!", i++);
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

#elif 0 // 任务线程数量

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void task_led(void *arg)
{
    int *a = (int *)arg;
    printf("zhe shi di de  = %p \n", &a);
    while (1)
    {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main(void)
{
    void *const pvParameters;
    int i = 0;
    for (i = 0; i < 10000; i++)
    {
        xTaskCreate(task_led, "led", 2048, &i, 10, NULL);
        printf("di ji de  = %d \n", i);
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
    printf("wanceng  = %d \n", i);
}

#elif 0 // nvs掉电保存
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "driver/gpio.h"

#define STORAGE_NAMESPACE "storage"

#if CONFIG_IDF_TARGET_ESP32C3
#define BOOT_MODE_PIN GPIO_NUM_9
#else
#define BOOT_MODE_PIN GPIO_NUM_0
#endif // CONFIG_IDF_TARGET_ESP32C3

/* 通过首先读取然后递增先前保存的数量，将模块重新启动的次数保存在 NVS 中。
如果在此过程中出现任何问题，则返回错误。
 */
esp_err_t save_restart_counter(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // Open
    err = nvs_open(STORAGE_NAMESPACE, // 命名空间名称
                   NVS_READWRITE,     // NVS_READWRITE 或 NVS_READONLY。如果 NVS_READONLY，将打开一个只读句柄。
                   &my_handle);       // 如果成功（返回代码为零），句柄将在此参数中返回。
    if (err != ESP_OK)
        return err;

    // Read
    int32_t restart_counter = 0; // 如果尚未在 NVS 中设置，值将默认为 0
    /*这些函数根据键的名称检索键的值*/
    err = nvs_get_i32(my_handle,         // 从nvs_open函数获得的句柄。
                      "restart_conter",  // 键名。最大长度为 (NVS_KEY_NAME_MAX_SIZE-1) 个字符。不应该为空。
                      &restart_counter); // 指向输出值的指针。nvs_get_str 和 nvs_get_blob 可能为 NULL，在这种情况下，所需的长度将在长度参数中返回。
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
        return err;

    // Write
    restart_counter++;
    /*为给定键设置 指定类型的 值*/
    err = nvs_set_i32(my_handle,        // 从nvs_open函数获得的句柄。无法使用以只读方式打开的句柄。
                      "restart_conter", // 键名。最大长度为 (NVS_KEY_NAME_MAX_SIZE-1)(16-1) 15个字符。不应该为空。
                      restart_counter); // 要设置的值。
    if (err != ESP_OK)
        return err;

    // 承诺书面价值。设置任何值后，必须调用 nvs_commit() 以确保更改写入闪存。
    // 实现可能会在其他时间写入存储，但这不能保证。
    err = nvs_commit(my_handle);
    if (err != ESP_OK)
        return err;

    // 关闭存储句柄并释放所有分配的资源
    nvs_close(my_handle);
    return ESP_OK;
}

/* 通过首先读取先前保存的值的表，然后在表的末尾添加新值，
将新的运行时值保存在 NVS 中。如果在此过程中出现任何问题，则返回错误。
 */
esp_err_t save_run_time(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // Open
    err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK)
        return err;

    // 读取blob所需的内存空间大小
    size_t required_size = 0; // 如果尚未在 NVS 中设置，值将默认为 0
    /*获取给定键的 blob 值*/
    err = nvs_get_blob(my_handle,       // 从nvs_open函数获得的句柄。
                       "run_time",      // 键名。最大长度为15个字符。不应该为空。
                       NULL,            // 指向输出值的指针。
                       &required_size); // 指向保存 out_value 长度的变量的非零指针。
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
        return err;

    // 读取之前保存的 blob（如果可用）
    uint32_t *run_time = malloc(required_size + sizeof(uint32_t)); // malloc 动态分配内存
    if (required_size > 0)
    {
        err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);
        if (err != ESP_OK)
        {
            free(run_time); // 释放分配的动态内存
            return err;
        }
    }

    // 写入值，包括之前保存的 blob（如果可用）
    required_size += sizeof(uint32_t);
    // xTaskGetTickCount():自调用 vTaskStartScheduler 以来的时钟周期计数。
    run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;
    /*为给定键设置可变长度二进制值*/
    err = nvs_set_blob(my_handle,      // 从nvs_open函数获得的句柄。
                       "run_time",     // 键名。最大长度为 15个字符。不应该为空。
                       run_time,       // 要设置的值。
                       required_size); // 要设置的二进制值的长度（以字节为单位）；最大长度为 508000 字节或（分区大小的 97.6% - 4000）字节，以较小者为准。
    free(run_time);

    if (err != ESP_OK)
        return err;

    // Commit
    err = nvs_commit(my_handle);
    if (err != ESP_OK)
        return err;

    // Close
    nvs_close(my_handle);
    return ESP_OK;
}

/* 从 NVS 读取并打印重启计数器和运行时间表。如果在此过程中出现任何问题，则返回错误。
 */
esp_err_t print_what_saved(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // Open
    err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK)
        return err;

    // 读取重启计数器
    int32_t restart_counter = 0; // 如果尚未在 NVS 中设置，值将默认为 0
    err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
        return err;
    printf("Restart counter = %" PRIu32 "\n", restart_counter);

    // 读取运行时 blob
    size_t required_size = 0; // 如果尚未在 NVS 中设置，值将默认为 0
    // 获取所需的内存空间来存储从 NVS 读取的 blob
    err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
        return err;
    printf("Run time:\n");
    if (required_size == 0)
    {
        printf("Nothing saved yet!\n");
    }
    else
    {
        uint32_t *run_time = malloc(required_size);
        err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);
        if (err != ESP_OK)
        {
            free(run_time);
            return err;
        }
        for (int i = 0; i < required_size / sizeof(uint32_t); i++)
        {
            printf("%d: %" PRIu32 "\n", i + 1, run_time[i]);
        }
        free(run_time);
    }

    // Close
    nvs_close(my_handle);
    return ESP_OK;
}

esp_err_t nvs_du(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // Open
    err = nvs_open(STORAGE_NAMESPACE, // 命名空间名称
                   NVS_READWRITE,     // NVS_READWRITE 或 NVS_READONLY。如果 NVS_READONLY，将打开一个只读句柄。
                   &my_handle);       // 如果成功（返回代码为零），句柄将在此参数中返回。
    if (err != ESP_OK)
        return err;
    int32_t restart_counter = 0; // 如果尚未在 NVS 中设置，值将默认为 0
    int32_t zong = 0;
    char xm[15] = "aaaaaaaaaaaaaa";
    xm[14] = '\0';
    printf("Restarting...?????????\n");
    for (int ii = 13; ii > 0; ii--)
    {
        for (int i = 33; i <= 126; i++)
        {
            xm[ii] = i;
            zong++;
            /*这些函数根据键的名称检索键的值*/
            err = nvs_get_i32(my_handle, xm, &restart_counter); // 指向输出值的指针。nvs_get_str 和 nvs_get_blob 可能为 NULL，在这种情况下，所需的长度将在长度参数中返回。
            if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
            {
                return err;
            }
            vTaskDelay(200 / portTICK_PERIOD_MS);
            printf("xm  wei : %s, zong gong %ld ge, zhi = %ld\n", (char *)xm, zong, restart_counter);
        }
    }
    //  承诺书面价值。设置任何值后，必须调用 nvs_commit() 以确保更改写入闪存。
    //  实现可能会在其他时间写入存储，但这不能保证。
    err = nvs_commit(my_handle);
    if (err != ESP_OK)
        return err;

    // 关闭存储句柄并释放所有分配的资源
    nvs_close(my_handle);
    return ESP_OK;
}
esp_err_t nvs_tian_jia(void)
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // Open
    err = nvs_open(STORAGE_NAMESPACE, // 命名空间名称
                   NVS_READWRITE,     // NVS_READWRITE 或 NVS_READONLY。如果 NVS_READONLY，将打开一个只读句柄。
                   &my_handle);       // 如果成功（返回代码为零），句柄将在此参数中返回。
    if (err != ESP_OK)
        return err;
    int32_t restart_counter = 0; // 如果尚未在 NVS 中设置，值将默认为 0
    int32_t zong = 0;
    char xm[15] = "aaaaaaaaaaaaaa";
    xm[14] = '\0';
    for (int ii = 13; ii > 0; ii--)
    {
        for (int i = 33; i <= 126; i++)
        {
            xm[ii] = i;
            zong++;
            restart_counter++;
            /*这些函数根据键的名称检索键的值*/
            err = nvs_set_i32(my_handle, xm, restart_counter); // 指向输出值的指针。nvs_get_str 和 nvs_get_blob 可能为 NULL，在这种情况下，所需的长度将在长度参数中返回。
            if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
            {
                return err;
            }
            vTaskDelay(200 / portTICK_PERIOD_MS);
            printf("xm  wei : %s, zong gong %ld ge\n", (char *)xm, zong);
        }
    }
    //  承诺书面价值。设置任何值后，必须调用 nvs_commit() 以确保更改写入闪存。
    //  实现可能会在其他时间写入存储，但这不能保证。
    err = nvs_commit(my_handle);
    if (err != ESP_OK)
        return err;

    // 关闭存储句柄并释放所有分配的资源
    nvs_close(my_handle);
    return ESP_OK;
}

void app_main66(void)
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        // NVS分区被截断，需要擦除重试nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    err = print_what_saved();
    if (err != ESP_OK)
        printf("Error (%s) reading data from NVS!\n", esp_err_to_name(err));
    /*
        err = nvs_tian_jia();
        if (err != ESP_OK)
            printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));
        printf("nvs_tian_jia   ok...\n");*/
    err = nvs_du();
    if (err != ESP_OK)
        printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));
    printf("nvs_du   ok...\n");

    gpio_reset_pin(BOOT_MODE_PIN);
    gpio_set_direction(BOOT_MODE_PIN, GPIO_MODE_INPUT);

    /* 读取GPIO0的状态。如果 GPIO0 为低电平的时间超过 1000 ms，则保存模块的运行时间并重新启动它
     */
    while (1)
    {
        if (gpio_get_level(BOOT_MODE_PIN) == 0)
        {
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            if (gpio_get_level(BOOT_MODE_PIN) == 0)
            {
                err = save_run_time();
                if (err != ESP_OK)
                    printf("Error (%s) saving run time blob to NVS!\n", esp_err_to_name(err));
                printf("Restarting...\n");
                fflush(stdout);
                esp_restart();
            }
        }
        vTaskDelay(200 / portTICK_PERIOD_MS);
    }
}

#elif 0 // 睡眠模式
/* Power save Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

/*
   this example shows how to use power save mode
   set a router or a AP using the same SSID&PASSWORD as configuration of this example.
   start esp32 and when it connected to AP it will enter power save mode
*/
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_pm.h"
#include "nvs_flash.h"

/*set the ssid and password via "idf.py menuconfig"*/
#define DEFAULT_SSID "cccxxx"
#define DEFAULT_PWD "chenxin1234567890"

#define DEFAULT_LISTEN_INTERVAL 10
#define DEFAULT_BEACON_TIMEOUT 300

#if CONFIG_EXAMPLE_POWER_SAVE_MIN_MODEM
#define DEFAULT_PS_MODE WIFI_PS_MIN_MODEM
#elif CONFIG_EXAMPLE_POWER_SAVE_MAX_MODEM
#define DEFAULT_PS_MODE WIFI_PS_MAX_MODEM
#elif CONFIG_EXAMPLE_POWER_SAVE_NONE
#define DEFAULT_PS_MODE WIFI_PS_NONE
#else
#define DEFAULT_PS_MODE WIFI_PS_MIN_MODEM
#endif /*CONFIG_POWER_SAVE_MODEM*/

static const char *TAG = "power_save";

static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        esp_wifi_connect();
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        esp_wifi_connect();
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "got ip: " IPSTR, IP2STR(&event->ip_info.ip));
    }
}

/*将 wifi 初始化为 sta 并设置省电模式*/
static void wifi_power_save(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = DEFAULT_SSID,
            .password = DEFAULT_PWD,
            .listen_interval = DEFAULT_LISTEN_INTERVAL, // 省电模式下的接收信标的时间间隔
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    ESP_ERROR_CHECK(esp_wifi_set_inactive_time(WIFI_IF_STA, DEFAULT_BEACON_TIMEOUT));

    ESP_LOGI(TAG, "esp_wifi_set_ps().");
    esp_wifi_set_ps(DEFAULT_PS_MODE);
}

void app_main(void)
{
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

#if CONFIG_PM_ENABLE
    // Configure dynamic frequency scaling:
    // maximum and minimum frequencies are set in sdkconfig,
    // automatic light sleep is enabled if tickless idle support is enabled.
    esp_pm_config_t pm_config = {
        .max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
        .min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
        .light_sleep_enable = true
#endif
    };
    ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
#endif // CONFIG_PM_ENABLE

    wifi_power_save();
}

#elif 0 // 时间同步
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_sleep.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_netif_sntp.h"
#include "lwip/ip_addr.h"
#include "esp_sntp.h"

static const char *TAG = "example";

#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 48
#endif

/* 可变保持 ESP32 自首次启动以来重新启动的次数.
 * 它使用 RTC_DATA_ATTR 放入 RTC 内存中，并在 ESP32 从深度睡眠中唤醒时保持其值。
 */
RTC_DATA_ATTR static int boot_count = 0;

static void obtain_time(void);

#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_CUSTOM
void sntp_sync_time(struct timeval *tv)
{
    settimeofday(tv, NULL);
    ESP_LOGI(TAG, "Time is synchronized from custom code");
    sntp_set_sync_status(SNTP_SYNC_STATUS_COMPLETED);
}
#endif

void time_sync_notification_cb(struct timeval *tv)
{
    ESP_LOGI(TAG, "时间同步事件的通知\n");
}

void app_main(void)
{
    ++boot_count;
    ESP_LOGI(TAG, "Boot count: %d", boot_count);

    time_t now;
    struct tm timeinfo;
    time(&now);
    localtime_r(&now, &timeinfo);
    // 时间定了吗？如果不是，tm_year 将是 (1970 - 1900)..
    if (timeinfo.tm_year < (2016 - 1900))
    {
        ESP_LOGI(TAG, "时间尚未确定。连接到 WiFi 并通过 NTP 获取时间.");
        obtain_time();
        // 用当前时间更新“now”变量
        time(&now);
    }
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
    else
    {
        // add 500 ms error to the current system time.
        // Only to demonstrate a work of adjusting method!
        {
            ESP_LOGI(TAG, "Add a error for test adjtime");
            struct timeval tv_now;
            gettimeofday(&tv_now, NULL);
            int64_t cpu_time = (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec;
            int64_t error_time = cpu_time + 500 * 1000L;
            struct timeval tv_error = {.tv_sec = error_time / 1000000L, .tv_usec = error_time % 1000000L};
            settimeofday(&tv_error, NULL);
        }

        ESP_LOGI(TAG, "Time was set, now just adjusting it. Use SMOOTH SYNC method.");
        obtain_time();
        // update 'now' variable with current time
        time(&now);
    }
#endif

    char strftime_buf[64];

    // 将时区设置为东部标准时间并打印当地时间
    setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
    tzset();
    localtime_r(&now, &timeinfo);
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf);

    // 将时区设置为中国标准时间
    setenv("TZ", "CST-8", 1);
    tzset();
    localtime_r(&now, &timeinfo);
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);

    if (sntp_get_sync_mode() == SNTP_SYNC_MODE_SMOOTH)
    {
        struct timeval outdelta;
        while (sntp_get_sync_status() == SNTP_SYNC_STATUS_IN_PROGRESS)
        {
            adjtime(NULL, &outdelta);
            ESP_LOGI(TAG, "等待调整时间...outdelta = %jd sec: %li ms: %li us\n",
                     (intmax_t)outdelta.tv_sec,
                     outdelta.tv_usec / 1000,
                     outdelta.tv_usec % 1000);
            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }
    }

    const int deep_sleep_sec = 10;
    ESP_LOGI(TAG, "进入深度睡眠 %d 秒", deep_sleep_sec);
    esp_deep_sleep(1000000LL * deep_sleep_sec);
}

static void print_servers(void)
{
    ESP_LOGI(TAG, "配置的 NTP 服务器列表:\n");

    for (uint8_t i = 0; i < SNTP_MAX_SERVERS; ++i)
    {
        if (esp_sntp_getservername(i))
        {
            ESP_LOGI(TAG, "server %d: %s", i, esp_sntp_getservername(i));
        }
        else
        {
            // 我们有 IPv4 或 IPv6 地址，让我们打印它
            char buff[INET6_ADDRSTRLEN];
            ip_addr_t const *ip = esp_sntp_getserver(i);
            if (ipaddr_ntoa_r(ip, buff, INET6_ADDRSTRLEN) != NULL)
                ESP_LOGI(TAG, "server %d: %s", i, buff);
        }
    }
}

static void obtain_time(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

#if LWIP_DHCP_GET_NTP_SRV
    /**
     * NTP server address could be acquired via DHCP,
     * see following menuconfig options:
     * 'LWIP_DHCP_GET_NTP_SRV' - enable STNP over DHCP
     * 'LWIP_SNTP_DEBUG' - enable debugging messages
     *
     * NOTE: This call should be made BEFORE esp acquires IP address from DHCP,
     * otherwise NTP option would be rejected by default.
     */
    ESP_LOGI(TAG, "Initializing SNTP");
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);
    config.start = false;                     // start SNTP service explicitly (after connecting)
    config.server_from_dhcp = true;           // accept NTP offers from DHCP server, if any (need to enable *before* connecting)
    config.renew_servers_after_new_IP = true; // let esp-netif update configured SNTP server(s) after receiving DHCP lease
    config.index_of_first_server = 1;         // updates from server num 1, leaving server 0 (from DHCP) intact
    // configure the event on which we renew servers
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
    config.ip_event_to_renew = IP_EVENT_STA_GOT_IP;
#else
    config.ip_event_to_renew = IP_EVENT_ETH_GOT_IP;
#endif
    config.sync_cb = time_sync_notification_cb; // only if we need the notification function
    esp_netif_sntp_init(&config);

#endif /* LWIP_DHCP_GET_NTP_SRV */

    /* 此帮助函数配置 Wi-Fi 或以太网，如在 menuconfig 中选择的那样。
    有关此功能的更多信息，请阅读 Examples/protocols/README.md 中的“建立 Wi-Fi 或以太网连接”部分。
     */
    ESP_ERROR_CHECK(example_connect());

#if LWIP_DHCP_GET_NTP_SRV
    ESP_LOGI(TAG, "Starting SNTP");
    esp_netif_sntp_start();
#if LWIP_IPV6 && SNTP_MAX_SERVERS > 2
    /* This demonstrates using IPv6 address as an additional SNTP server
     * (statically assigned IPv6 address is also possible)
     */
    ip_addr_t ip6;
    if (ipaddr_aton("2a01:3f7::1", &ip6))
    { // ipv6 ntp source "ntp.netnod.se"
        esp_sntp_setserver(2, &ip6);
    }
#endif /* LWIP_IPV6 */

#else
    ESP_LOGI(TAG, "初始化并启动 SNTP");
#if CONFIG_LWIP_SNTP_MAX_SERVERS > 1
    /* 这演示了配置多个服务器
     */
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(2,
                                                                      ESP_SNTP_SERVER_LIST(CONFIG_SNTP_TIME_SERVER, "pool.ntp.org"));
#else
    /*
     * 这是一台服务器并启动服务的基本默认配置
     */
    esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_SNTP_TIME_SERVER);
#endif
    config.sync_cb = time_sync_notification_cb; // 注意：只有当我们想要时才需要
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
    config.smooth_sync = true;
#endif

    esp_netif_sntp_init(&config);
#endif

    print_servers();

    // 等待时间设定
    time_t now = 0;
    struct tm timeinfo = {0};
    int retry = 0;
    const int retry_count = 15;
    while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count)
    {
        ESP_LOGI(TAG, "等待系统时间设置... (%d/%d)\n", retry, retry_count);
    }
    time(&now);
    localtime_r(&now, &timeinfo);

    ESP_ERROR_CHECK(example_disconnect());
    esp_netif_sntp_deinit();
}

#elif 0 // ping IP
#include "c3_main.h"
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_console.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "argtable3/argtable3.h"
#include "protocol_examples_common.h"
#include "ping/ping_sock.h"

static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
    uint8_t ttl;
    uint16_t seqno;
    uint32_t elapsed_time, recv_len;
    ip_addr_t target_addr;
    esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
    esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
    esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
    esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
    printf("%" PRIu32 " bytes from %s icmp_seq=%" PRIu16 " ttl=%" PRIu16 " time=%" PRIu32 " ms\n",
           recv_len, ipaddr_ntoa((ip_addr_t *)&target_addr), seqno, ttl, elapsed_time);
}

static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
    uint16_t seqno;
    ip_addr_t target_addr;
    esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
    printf("From %s icmp_seq=%d timeout\n", ipaddr_ntoa((ip_addr_t *)&target_addr), seqno);
}

static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
    ip_addr_t target_addr;
    uint32_t transmitted;
    uint32_t received;
    uint32_t total_time_ms;
    esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
    esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
    esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
    uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
    if (IP_IS_V4(&target_addr))
    {
        printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
    }
    else
    {
        printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
    }
    printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n",
           transmitted, received, loss, total_time_ms);
    // 删除 ping 会话，以便我们清理所有资源并可以创建新的 ping 会话
    // 我们不必在回调中调用删除函数，而是可以从其他任务中调用删除函数
    esp_ping_delete_session(hdl);
}

static struct
{
    struct arg_dbl *timeout;
    struct arg_dbl *interval;
    struct arg_int *data_size;
    struct arg_int *count;
    struct arg_int *tos;
    struct arg_int *ttl;
    struct arg_str *host;
    struct arg_end *end;
} ping_args;

static int do_ping_cmd(int argc, char **argv)
{
    esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();

    int nerrors = arg_parse(argc, argv, (void **)&ping_args);
    if (nerrors != 0)
    {
        arg_print_errors(stderr, ping_args.end, argv[0]);
        return 1;
    }

    if (ping_args.timeout->count > 0)
    {
        config.timeout_ms = (uint32_t)(ping_args.timeout->dval[0] * 1000);
    }

    if (ping_args.interval->count > 0)
    {
        config.interval_ms = (uint32_t)(ping_args.interval->dval[0] * 1000);
    }

    if (ping_args.data_size->count > 0)
    {
        config.data_size = (uint32_t)(ping_args.data_size->ival[0]);
    }

    if (ping_args.count->count > 0)
    {
        config.count = (uint32_t)(ping_args.count->ival[0]);
    }

    if (ping_args.tos->count > 0)
    {
        config.tos = (uint32_t)(ping_args.tos->ival[0]);
    }

    if (ping_args.ttl->count > 0)
    {
        config.ttl = (uint32_t)(ping_args.ttl->ival[0]);
    }

    // 解析IP地址
    struct sockaddr_in6 sock_addr6;
    ip_addr_t target_addr;
    memset(&target_addr, 0, sizeof(target_addr)); // 清0

    if (inet_pton(AF_INET6, ping_args.host->sval[0], &sock_addr6.sin6_addr) == 1)
    {
        /* 将 ip6 字符串转换为 ip6 地址 */
        ipaddr_aton(ping_args.host->sval[0], &target_addr);
    }
    else
    {
        struct addrinfo hint;
        struct addrinfo *res = NULL;
        memset(&hint, 0, sizeof(hint));
        /*将 ip4 字符串或主机名转换为 ip4 或 ip6 地址 */
        if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0)
        {
            printf("ping: unknown host %s\n", ping_args.host->sval[0]);
            return 1;
        }
        if (res->ai_family == AF_INET)
        {
            struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr; // u32_t addr4
            inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
        }
        else
        {
            struct in6_addr addr6 = ((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr;
            inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
        }
        freeaddrinfo(res);
    }
    // config.target_addr = target_addr;
    ip_addr_t target_add;
    struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
    const char *ip_str = "192.168.1.1"; // 要赋值的IP地址字符串
    if (getaddrinfo(ip_str, NULL, &hints, &res) != 0)
    {
        printf("ping: unknown host1 %s\n", ping_args.host->sval[0]);
        return 1;
    }
    if (res->ai_family == AF_INET)
    {
        struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr; // u32_t addr4
        inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
        config.target_addr = target_addr;
        printf("OK\n");
        freeaddrinfo(res);
    }else{
        printf("no\n");
        config.target_addr = target_addr;
    }

    /* 设置回调函数 */
    esp_ping_callbacks_t cbs = {
        .cb_args = NULL,
        .on_ping_success = cmd_ping_on_ping_success,
        .on_ping_timeout = cmd_ping_on_ping_timeout,
        .on_ping_end = cmd_ping_on_ping_end};
    esp_ping_handle_t ping;
    esp_ping_new_session(&config, &cbs, &ping);
    esp_ping_start(ping);

    return 0;
}

static void register_ping(void)
{
    ping_args.timeout = arg_dbl0("W", "timeout", "<t>", "等待响应的时间（以秒为单位）");
    ping_args.interval = arg_dbl0("i", "interval", "<t>", "发送每个数据包之间的等待间隔秒数");
    ping_args.data_size = arg_int0("s", "size", "<n>", "指定要发送的数据字节数");
    ping_args.count = arg_int0("c", "count", "<n>", "发送count个数据包后停止");
    ping_args.tos = arg_int0("Q", "tos", "<n>", "设置 IP 数据报中服务类型相关位");
    ping_args.ttl = arg_int0("T", "ttl", "<n>", "设置 IP 数据报中的生存时间相关位");
    ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
    ping_args.end = arg_end(1);
    const esp_console_cmd_t ping_cmd = {
        .command = "ping",
        .help = "send ICMP ECHO_REQUEST to network hosts",
        .hint = NULL,
        .func = &do_ping_cmd,
        .argtable = &ping_args};
    ESP_ERROR_CHECK(esp_console_cmd_register(&ping_cmd));
}

static esp_console_repl_t *s_repl = NULL;

/* handle 'quit' command */
static int do_cmd_quit(int argc, char **argv)
{
    printf("ByeBye\r\n");
    s_repl->del(s_repl);
    return 0;
}

static esp_err_t register_quit(void)
{
    esp_console_cmd_t command = {
        .command = "quit",
        .help = "Quit REPL environment",
        .func = &do_cmd_quit};
    return esp_console_cmd_register(&command);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
    // 安装控制台REPL环境
#if CONFIG_ESP_CONSOLE_UART
    esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl));
#elif CONFIG_ESP_CONSOLE_USB_CDC
    esp_console_dev_usb_cdc_config_t cdc_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&cdc_config, &repl_config, &s_repl));
#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
    esp_console_dev_usb_serial_jtag_config_t usbjtag_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&usbjtag_config, &repl_config, &repl));
#endif

    /* Use either WiFi console commands or menuconfig options to connect to WiFi/Ethernet
     *
     * Please disable `Provide wifi connect commands` in `Example Connection Configuration`
     * to connect immediately using configured interface and settings (WiFi/Ethernet).
     */
#if defined(CONFIG_EXAMPLE_PROVIDE_WIFI_CONSOLE_CMD)
    /* register wifi connect commands */
    example_register_wifi_connect_commands();
#elif defined(CONFIG_EXAMPLE_CONNECT_WIFI) || defined(CONFIG_EXAMPLE_CONNECT_ETHERNET)
    /* automatic connection per menuconfig */
    ESP_ERROR_CHECK(example_connect());
#endif

    /* register command `ping` */
    register_ping();
    /* register command `quit` */
    register_quit();

    // start console REPL
    ESP_ERROR_CHECK(esp_console_start_repl(s_repl));
}
#elif 0

/*
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

/****************************************************************************
*
* 本演示展示了 BLE GATT 服务器。它可以发送广播数据，被客户端连接。
* 运行 gatt_client 演示，客户端演示将自动连接到 gatt_server 演示。
* 客户端演示将在连接后启用 gatt_server 的通知。两个设备将交换数据。
*
****************************************************************************/


#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"

#include "esp_gap_ble_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"

#include "sdkconfig.h"

#include "freertos/semphr.h"

#define LOG_TAG "MULTI_ADV_DEMO"

// 定义一个宏，用于发送消息并等待信号量
#define FUNC_SEND_WAIT_SEM(func, sem) do {\
        esp_err_t __err_rc = (func);\
        if (__err_rc != ESP_OK) { \
            ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \
        } \
        xSemaphoreTake(sem, portMAX_DELAY); \
} while(0);
// 定义一个信号量，用于等待消息发送完成
static SemaphoreHandle_t test_sem = NULL;

// 定义一个 1M PHY 的地址
uint8_t addr_1m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x01};

// 定义一个 1M PHY 的扩展广播参数
esp_ble_gap_ext_adv_params_t ext_adv_params_1M = {
    .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE, // 广播类型为可连接
    .interval_min = 0x30, // 广播间隔最小值
    .interval_max = 0x30, // 广播间隔最大值
    .channel_map = ADV_CHNL_ALL, // 广播信道
    .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, // 过滤策略
    .primary_phy = ESP_BLE_GAP_PHY_1M, // 主要物理层
    .max_skip = 0, // 最大跳过次数
    .secondary_phy = ESP_BLE_GAP_PHY_1M, // 次要物理层
    .sid = 0, // 广播 ID
    .scan_req_notif = false, // 扫描请求通知
    .own_addr_type = BLE_ADDR_TYPE_RANDOM, // 自身地址类型
    .tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE, // 发射功率
};

// 定义 1M PHY 的扩展广播数据
static uint8_t raw_adv_data_1m[] = {
        0x02, 0x01, 0x06, // 广播标志
        0x02, 0x0a, 0xeb, // 16 位厂商 ID
        0x11, 0x09, 'E', 'S', 'P', '_', 'M', 'U', 'L', 'T', 'I', '_', 'A', // 设备名称
        'D', 'V', '_', '1', 'M'
};

// 定义 1M PHY 的扩展广播参数
esp_ble_gap_ext_adv_t ext_adv[4] = {
    // 实例，持续时间，间隔
    [0] = {0, 0, 0}
};

// 定义 GAP 事件处理函数
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event) {
    case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT:
        // 设置随机地址完成事件
        xSemaphoreGive(test_sem);
        ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT, status %d", param->ext_adv_set_rand_addr.status);
        break;
    case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
        // 设置参数完成事件
        xSemaphoreGive(test_sem);
        ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->ext_adv_set_params.status);
        break;
    case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
        // 设置广播数据完成事件
        xSemaphoreGive(test_sem);
        ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT, status %d", param->ext_adv_data_set.status);
        break;
    case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT:
        // 设置扫描响应数据完成事件
        xSemaphoreGive(test_sem);
        ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT, status %d", param->scan_rsp_set.status);
        break;
    case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
        // 启动广播完成事件
        xSemaphoreGive(test_sem);
        ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, status %d", param->ext_adv_start.status);
        break;
    case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT:
        // 停止广播完成事件
        xSemaphoreGive(test_sem);
        ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT, status %d", param->ext_adv_stop.status);
        break;
    default:
        break;
    }
}

void app_main(void)
{
    esp_err_t ret;

    // 初始化 NVS
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );

    // 释放经典蓝牙控制器
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    // 初始化蓝牙控制器
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret) {
        ESP_LOGE(LOG_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    // 启用蓝牙控制器
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret) {
        ESP_LOGE(LOG_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    // 初始化蓝牙协议栈
    ret = esp_bluedroid_init();
    if (ret) {
        ESP_LOGE(LOG_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    // 启用蓝牙协议栈
    ret = esp_bluedroid_enable();
    if (ret) {
        ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    // 注册 GAP 事件处理函数
    ret = esp_ble_gap_register_callback(gap_event_handler);
    if (ret){
        ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret);
        return;
    }

    vTaskDelay(200 / portTICK_PERIOD_MS);

    // 创建信号量
    test_sem = xSemaphoreCreateBinary();

    // 设置 1M PHY 的扩展广播参数
    FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(0, &ext_adv_params_1M), test_sem);

    // 设置 1M PHY 的扩展广播随机地址
    FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_rand_addr(0, addr_1m), test_sem);

    // 配置 1M PHY 的扩展广播数据
    FUNC_SEND_WAIT_SEM(esp_ble_gap_config_ext_adv_data_raw(0, sizeof(raw_adv_data_1m), &raw_adv_data_1m[0]), test_sem);

    // 启动所有广播
    FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_start(4, &ext_adv[0]), test_sem);

    return;
}




#elif 0 //蓝牙5.0server
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"

#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "ble50_sec_gatts_demo.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Attributes State Machine */


#define GATTS_TABLE_TAG "SEC_GATTS_DEMO"

#define HEART_PROFILE_NUM                         1
#define HEART_PROFILE_APP_IDX                     0
#define ESP_HEART_RATE_APP_ID                     0x55
#define HEART_RATE_SVC_INST_ID                    0
#define EXT_ADV_HANDLE                            0
#define NUM_EXT_ADV_SET                           1
#define EXT_ADV_DURATION                          0
#define EXT_ADV_MAX_EVENTS                        0

#define GATTS_DEMO_CHAR_VAL_LEN_MAX               0x40

static uint16_t profile_handle_table[HRS_IDX_NB];

static uint8_t ext_adv_raw_data[] = {
        0x02, 0x01, 0x06,
        0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd,
        0x11, 0X09,  'B', 'L', 'E', '5', '0', '_', 'T', 'H', 'R', 'O', 'U', 'G', 'H','P', 'U', 'T',
};

static esp_ble_gap_ext_adv_t ext_adv[1] = {
    [0] = {EXT_ADV_HANDLE, EXT_ADV_DURATION, EXT_ADV_MAX_EVENTS},
};

esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
    .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
    .interval_min = 0x20,
    .interval_max = 0x20,
    .channel_map = ADV_CHNL_ALL,
    .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
    .primary_phy = ESP_BLE_GAP_PHY_1M,
    .max_skip = 0,
    .secondary_phy = ESP_BLE_GAP_PHY_2M,
    .sid = 0,
    .scan_req_notif = false,
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE,
};

struct gatts_profile_inst {
    esp_gatts_cb_t gatts_cb;
    uint16_t gatts_if;
    uint16_t app_id;
    uint16_t conn_id;
    uint16_t service_handle;
    esp_gatt_srvc_id_t service_id;
    uint16_t char_handle;
    esp_bt_uuid_t char_uuid;
    esp_gatt_perm_t perm;
    esp_gatt_char_prop_t property;
    uint16_t descr_handle;
    esp_bt_uuid_t descr_uuid;
};

static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
                                        esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);

/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
    [HEART_PROFILE_APP_IDX] = {
        .gatts_cb = gatts_profile_event_handler,
        .gatts_if = ESP_GATT_IF_NONE,       /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
    },

};

/* Service */
static const uint16_t GATTS_SERVICE_UUID_TEST      = 0x00FF;
static const uint16_t GATTS_CHAR_UUID_TEST_A       = 0xFF01;
static const uint16_t GATTS_CHAR_UUID_TEST_B       = 0xFF02;
static const uint16_t GATTS_CHAR_UUID_TEST_C       = 0xFF03;

static const uint16_t primary_service_uuid         = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid   = ESP_GATT_UUID_CHAR_DECLARE;
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
static const uint8_t char_prop_read                =  ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char_prop_write               = ESP_GATT_CHAR_PROP_BIT_WRITE;
static const uint8_t char_prop_read_write_notify   = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t heart_measurement_ccc[2]      = {0x00, 0x00};
static const uint8_t char_value[4]                 = {0x11, 0x22, 0x33, 0x44};
#define CHAR_DECLARATION_SIZE       (sizeof(uint8_t))
#define SVC_INST_ID                 0


/* 完整数据库描述 - 用于将属性添加到数据库中 */
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{
    // Service Declaration
    [IDX_SVC]        =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
      sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},

    /* Characteristic Declaration */
    [IDX_CHAR_A]     =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},

    /* Characteristic Value */
    [IDX_CHAR_VAL_A] =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

    /* Client Characteristic Configuration Descriptor */
    [IDX_CHAR_CFG_A]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},

    /* Characteristic Declaration */
    [IDX_CHAR_B]      =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},

    /* Characteristic Value */
    [IDX_CHAR_VAL_B]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

    /* Characteristic Declaration */
    [IDX_CHAR_C]      =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}},

    /* Characteristic Value */
    [IDX_CHAR_VAL_C]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

};

static char *esp_key_type_to_str(esp_ble_key_type_t key_type)
{
   char *key_str = NULL;
   switch(key_type) {
    case ESP_LE_KEY_NONE:
        key_str = "ESP_LE_KEY_NONE";
        break;
    case ESP_LE_KEY_PENC:
        key_str = "ESP_LE_KEY_PENC";
        break;
    case ESP_LE_KEY_PID:
        key_str = "ESP_LE_KEY_PID";
        break;
    case ESP_LE_KEY_PCSRK:
        key_str = "ESP_LE_KEY_PCSRK";
        break;
    case ESP_LE_KEY_PLK:
        key_str = "ESP_LE_KEY_PLK";
        break;
    case ESP_LE_KEY_LLK:
        key_str = "ESP_LE_KEY_LLK";
        break;
    case ESP_LE_KEY_LENC:
        key_str = "ESP_LE_KEY_LENC";
        break;
    case ESP_LE_KEY_LID:
        key_str = "ESP_LE_KEY_LID";
        break;
    case ESP_LE_KEY_LCSRK:
        key_str = "ESP_LE_KEY_LCSRK";
        break;
    default:
        key_str = "INVALID BLE KEY TYPE";
        break;

   }

   return key_str;
}

static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
{
   char *auth_str = NULL;
   switch(auth_req) {
    case ESP_LE_AUTH_NO_BOND:
        auth_str = "ESP_LE_AUTH_NO_BOND";
        break;
    case ESP_LE_AUTH_BOND:
        auth_str = "ESP_LE_AUTH_BOND";
        break;
    case ESP_LE_AUTH_REQ_MITM:
        auth_str = "ESP_LE_AUTH_REQ_MITM";
        break;
    case ESP_LE_AUTH_REQ_BOND_MITM:
        auth_str = "ESP_LE_AUTH_REQ_BOND_MITM";
        break;
    case ESP_LE_AUTH_REQ_SC_ONLY:
        auth_str = "ESP_LE_AUTH_REQ_SC_ONLY";
        break;
    case ESP_LE_AUTH_REQ_SC_BOND:
        auth_str = "ESP_LE_AUTH_REQ_SC_BOND";
        break;
    case ESP_LE_AUTH_REQ_SC_MITM:
        auth_str = "ESP_LE_AUTH_REQ_SC_MITM";
        break;
    case ESP_LE_AUTH_REQ_SC_MITM_BOND:
        auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND";
        break;
    default:
        auth_str = "INVALID BLE AUTH REQ";
        break;
   }

   return auth_str;
}

static void show_bonded_devices(void)
{
    int dev_num = esp_ble_get_bond_device_num();

    esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
    esp_ble_get_bond_device_list(&dev_num, dev_list);
    ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices number : %d", dev_num);
    for (int i = 0; i < dev_num; i++) {
        esp_log_buffer_hex(GATTS_TABLE_TAG, (void *)dev_list[i].bd_addr, sizeof(esp_bd_addr_t));
    }

    free(dev_list);
}

static void __attribute__((unused)) remove_all_bonded_devices(void)
{
    int dev_num = esp_ble_get_bond_device_num();

    esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
    esp_ble_get_bond_device_list(&dev_num, dev_list);
    for (int i = 0; i < dev_num; i++) {
        esp_ble_remove_bond_device(dev_list[i].bd_addr);
    }

    free(dev_list);
}

/*事件回调*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event) {
    case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT:
        ESP_LOGI(GATTS_TABLE_TAG,"ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT status %d",  param->ext_adv_set_params.status);
        esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE,  sizeof(ext_adv_raw_data), &ext_adv_raw_data[0]);
        break;
    case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT:
         ESP_LOGI(GATTS_TABLE_TAG,"ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT status %d",  param->ext_adv_data_set.status);
         esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
         break;
    case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT:
         ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, status = %d", param->ext_adv_data_set.status);
        break;
    case ESP_GAP_BLE_ADV_TERMINATED_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_ADV_TERMINATED_EVT, status = %d", param->adv_terminate.status);
        if(param->adv_terminate.status == 0x00) {
            ESP_LOGI(GATTS_TABLE_TAG, "ADV successfully ended with a connection being created");
        }
        break;
    case ESP_GAP_BLE_PASSKEY_REQ_EVT:                           /* 密钥请求事件 */
        /* 调用以下函数输入远程设备上显示的密码*/
        //esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
        break;
    case ESP_GAP_BLE_OOB_REQ_EVT: {
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_OOB_REQ_EVT");
        uint8_t tk[16] = {1}; //如果与 OOB 配对，两个设备需要使用相同的 tk
        esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
        break;
    }
    case ESP_GAP_BLE_LOCAL_IR_EVT:                               /* BLE local IR event */
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT");
        break;
    case ESP_GAP_BLE_LOCAL_ER_EVT:                               /* BLE local ER event */
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT");
        break;
    case ESP_GAP_BLE_NC_REQ_EVT:
        /* 当 IO 具有 DisplayYesNO 功能并且对等设备 IO 也具有 DisplayYesNo 功能时，
        应用程序将收到此事件。向用户显示密钥号码，以与对等设备显示的号码进行确认。 */
        esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%" PRIu32, param->ble_security.key_notif.passkey);
        break;
    case ESP_GAP_BLE_SEC_REQ_EVT:
        /* 向对等设备发送肯定（true）安全响应以接受安全请求。
        如果不接受安全请求，应发送带有否定（假）接受值的安全响应*/
        esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
        break;
    case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:  ///当 IO 具有输出能力并且对等设备 IO 具有输入能力时，应用程序将收到此事件.
        ///向用户显示密钥号码，以便将其输入到对等设备中.
        ESP_LOGI(GATTS_TABLE_TAG, "The passkey Notify number:%06" PRIu32, param->ble_security.key_notif.passkey);
        break;
    case ESP_GAP_BLE_KEY_EVT:
        //向用户显示与对等设备共享的 ble 密钥信息。
        ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
        break;
    case ESP_GAP_BLE_AUTH_CMPL_EVT: {
        esp_bd_addr_t bd_addr;
        memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
        ESP_LOGI(GATTS_TABLE_TAG, "remote BD_ADDR: %08x%04x",\
                (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
                (bd_addr[4] << 8) + bd_addr[5]);
        ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
        ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
        if(!param->ble_security.auth_cmpl.success) {
            ESP_LOGI(GATTS_TABLE_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
        } else {
            ESP_LOGI(GATTS_TABLE_TAG, "auth mode = %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
        }
        show_bonded_devices();
        break;
    }
    case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
        ESP_LOGD(GATTS_TABLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT status = %d", param->remove_bond_dev_cmpl.status);
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV");
        ESP_LOGI(GATTS_TABLE_TAG, "-----ESP_GAP_BLE_REMOVE_BOND_DEV----");
        esp_log_buffer_hex(GATTS_TABLE_TAG, (void *)param->remove_bond_dev_cmpl.bd_addr, sizeof(esp_bd_addr_t));
        ESP_LOGI(GATTS_TABLE_TAG, "------------------------------------");
        break;
    }
    case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
        ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT, tatus = %x", param->local_privacy_cmpl.status);
        esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M);
        break;
    case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
         ESP_LOGI(GATTS_TABLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
                  param->update_conn_params.status,
                  param->update_conn_params.min_int,
                  param->update_conn_params.max_int,
                  param->update_conn_params.conn_int,
                  param->update_conn_params.latency,
                  param->update_conn_params.timeout);
        break;
    default:
        break;
    }
}

static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
                                        esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    switch (event) {
        case ESP_GATTS_REG_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_REG_EVT");
            //生成可解析的随机地址
            esp_ble_gap_config_local_privacy(true);
            esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
            break;
        case ESP_GATTS_READ_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT");
            break;
        case ESP_GATTS_WRITE_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT, write value:");
            esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len);
            break;
        case ESP_GATTS_EXEC_WRITE_EVT:
            break;
        case ESP_GATTS_MTU_EVT:
            break;
        case ESP_GATTS_CONF_EVT:
            break;
        case ESP_GATTS_UNREG_EVT:
            break;
        case ESP_GATTS_DELETE_EVT:
            break;
        case ESP_GATTS_START_EVT:
            break;
        case ESP_GATTS_STOP_EVT:
            break;
        case ESP_GATTS_CONNECT_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT");
            /* 当收到主设备发送的连接事件时，启动与对等设备的安全连接 */
            esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
            break;
        case ESP_GATTS_DISCONNECT_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
            /* 失去连接时再次开始广告 */
            esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]);
            break;
        case ESP_GATTS_OPEN_EVT:
            break;
        case ESP_GATTS_CANCEL_OPEN_EVT:
            break;
        case ESP_GATTS_CLOSE_EVT:
            break;
        case ESP_GATTS_LISTEN_EVT:
            break;
        case ESP_GATTS_CONGEST_EVT:
            break;
        case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
            ESP_LOGI(GATTS_TABLE_TAG, "The number handle = %x",param->add_attr_tab.num_handle);
            if (param->create.status == ESP_GATT_OK){
                if(param->add_attr_tab.num_handle == HRS_IDX_NB) {
                    memcpy(profile_handle_table, param->add_attr_tab.handles,
                    sizeof(profile_handle_table));
                   esp_ble_gatts_start_service(profile_handle_table[IDX_SVC]);
                }else{
                    ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)",
                         param->add_attr_tab.num_handle, HRS_IDX_NB);
                }
            }else{
                ESP_LOGE(GATTS_TABLE_TAG, " Create attribute table failed, error code = %x", param->create.status);
            }
        break;
    }

        default:
           break;
    }
}

/*注册事件回调*/
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
                                esp_ble_gatts_cb_param_t *param)
{
    /*如果事件是注册事件，则存储每个配置文件的 gatts_if */
    if (event == ESP_GATTS_REG_EVT) {
        if (param->reg.status == ESP_GATT_OK) {
            heart_rate_profile_tab[HEART_PROFILE_APP_IDX].gatts_if = gatts_if;
        } else {
            ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",
                    param->reg.app_id,
                    param->reg.status);
            return;
        }
    }

    do {
        int idx;
        for (idx = 0; idx < HEART_PROFILE_NUM; idx++) {
            if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, 不指定某个gatt_if，需要调用每个profile的cb函数 */
                    gatts_if == heart_rate_profile_tab[idx].gatts_if) {
                if (heart_rate_profile_tab[idx].gatts_cb) {
                    heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
                }
            }
        }
    } while (0);
}

void app_main(void)
{
    esp_err_t ret;

    // 初始化 NVS。
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret) {
        ESP_LOGE(GATTS_TABLE_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret) {
        ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth", __func__);
    ret = esp_bluedroid_init();
    if (ret) {
        ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bluedroid_enable();
    if (ret) {
        ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_ble_gatts_register_callback(gatts_event_handler);
    if (ret){
        ESP_LOGE(GATTS_TABLE_TAG, "gatts register error, error code = %x", ret);
        return;
    }
    ret = esp_ble_gap_register_callback(gap_event_handler);
    if (ret){
        ESP_LOGE(GATTS_TABLE_TAG, "gap register error, error code = %x", ret);
        return;
    }
    ret = esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
    if (ret){
        ESP_LOGE(GATTS_TABLE_TAG, "gatts 应用程序注册错误, error code = %x", ret);
        return;
    }

    /* 将安全 iocap &amp; auth_req &amp; 密钥大小 &amp; init key 响应密钥参数设置到堆栈*/
    esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;     //认证后与对端设备绑定
    esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;           //将IO能力设置为无输出无输入
    uint8_t key_size = 16;      //密钥大小应为 7~16 字节
    uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
    uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
    //设置静态密钥
    uint32_t passkey = 123456;
    uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE;
    uint8_t oob_support = ESP_BLE_OOB_DISABLE;
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
    /* 如果您的BLE设备作为Slave，
    init_key表示您希望Master的哪种类型的密钥应该分发给您，response key表示您可以分发给Master的哪种密钥;
      如果您的 BLE 设备作为主设备，
    则响应密钥表示您希望从设备应分配哪种类型的密钥给您，初始化密钥表示您可以分配给从设备的哪种密钥。 */
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));

    /* 只是展示如何清除所有绑定的设备 延迟30s，清除所有绑定的设备
     *
     * vTaskDelay(30000 / portTICK_PERIOD_MS);
     * remove_all_bonded_devices();
     */
    while (1)
    {
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
    
}
#elif 0 //蓝牙5.0client
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <inttypes.h>
#include "nvs.h"
#include "nvs_flash.h"

#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"

#define GATTC_TAG             "SEC_GATTC_DEMO"
#define REMOTE_SERVICE_UUID   0x00FF
#define REMOTE_NOTIFY_UUID    0xFF01
#define EXT_SCAN_DURATION     0
#define EXT_SCAN_PERIOD       0


static esp_gattc_char_elem_t *char_elem_result   = NULL;
static esp_gattc_descr_elem_t *descr_elem_result = NULL;

///Declare static functions
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);


static esp_bt_uuid_t remote_filter_service_uuid = {
    .len = ESP_UUID_LEN_16,
    .uuid = {.uuid16 = REMOTE_SERVICE_UUID,},
};

static bool connect = false;
static bool get_service = false;
static const char remote_device_name[] = "ESP_BLE50_SERVER";

static esp_ble_ext_scan_params_t ext_scan_params = {
    .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
    .filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
    .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE,
    .cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK,
    .uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
    .coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40},
};

const esp_ble_gap_conn_params_t phy_1m_conn_params = {
    .scan_interval = 0x40,
    .scan_window = 0x40,
    .interval_min = 320,
    .interval_max = 320,
    .latency = 0,
    .supervision_timeout = 600,
    .min_ce_len  = 0,
    .max_ce_len = 0,
};
const esp_ble_gap_conn_params_t phy_2m_conn_params = {
    .scan_interval = 0x40,
    .scan_window = 0x40,
    .interval_min = 320,
    .interval_max = 320,
    .latency = 0,
    .supervision_timeout = 600,
    .min_ce_len  = 0,
    .max_ce_len = 0,
};
const esp_ble_gap_conn_params_t phy_coded_conn_params = {
    .scan_interval = 0x40,
    .scan_window = 0x40,
    .interval_min = 320, // 306-> 362Kbps
    .interval_max = 320,
    .latency = 0,
    .supervision_timeout = 600,
    .min_ce_len  = 0,
    .max_ce_len = 0,
};

#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
#define INVALID_HANDLE   0

struct gattc_profile_inst {
    esp_gattc_cb_t gattc_cb;
    uint16_t gattc_if;
    uint16_t app_id;
    uint16_t conn_id;
    uint16_t service_start_handle;
    uint16_t service_end_handle;
    uint16_t notify_char_handle;
    esp_bd_addr_t remote_bda;
};

/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
    [PROFILE_A_APP_ID] = {
        .gattc_cb = gattc_profile_event_handler,
        .gattc_if = ESP_GATT_IF_NONE,       /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
    },
};

static const char *esp_key_type_to_str(esp_ble_key_type_t key_type)
{
   const char *key_str = NULL;
   switch(key_type) {
    case ESP_LE_KEY_NONE:
        key_str = "ESP_LE_KEY_NONE";
        break;
    case ESP_LE_KEY_PENC:
        key_str = "ESP_LE_KEY_PENC";
        break;
    case ESP_LE_KEY_PID:
        key_str = "ESP_LE_KEY_PID";
        break;
    case ESP_LE_KEY_PCSRK:
        key_str = "ESP_LE_KEY_PCSRK";
        break;
    case ESP_LE_KEY_PLK:
        key_str = "ESP_LE_KEY_PLK";
        break;
    case ESP_LE_KEY_LLK:
        key_str = "ESP_LE_KEY_LLK";
        break;
    case ESP_LE_KEY_LENC:
        key_str = "ESP_LE_KEY_LENC";
        break;
    case ESP_LE_KEY_LID:
        key_str = "ESP_LE_KEY_LID";
        break;
    case ESP_LE_KEY_LCSRK:
        key_str = "ESP_LE_KEY_LCSRK";
        break;
    default:
        key_str = "INVALID BLE KEY TYPE";
        break;

    }
     return key_str;
}

static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req)
{
   char *auth_str = NULL;
   switch(auth_req) {
    case ESP_LE_AUTH_NO_BOND:
        auth_str = "ESP_LE_AUTH_NO_BOND";
        break;
    case ESP_LE_AUTH_BOND:
        auth_str = "ESP_LE_AUTH_BOND";
        break;
    case ESP_LE_AUTH_REQ_MITM:
        auth_str = "ESP_LE_AUTH_REQ_MITM";
        break;
    case ESP_LE_AUTH_REQ_BOND_MITM:
        auth_str = "ESP_LE_AUTH_REQ_BOND_MITM";
        break;
    case ESP_LE_AUTH_REQ_SC_ONLY:
        auth_str = "ESP_LE_AUTH_REQ_SC_ONLY";
        break;
    case ESP_LE_AUTH_REQ_SC_BOND:
        auth_str = "ESP_LE_AUTH_REQ_SC_BOND";
        break;
    case ESP_LE_AUTH_REQ_SC_MITM:
        auth_str = "ESP_LE_AUTH_REQ_SC_MITM";
        break;
    case ESP_LE_AUTH_REQ_SC_MITM_BOND:
        auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND";
        break;
    default:
        auth_str = "INVALID BLE AUTH REQ";
        break;
   }

   return auth_str;
}

static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
    esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;

    switch (event) {
    case ESP_GATTC_REG_EVT:
        ESP_LOGI(GATTC_TAG, "REG_EVT");
        esp_ble_gap_config_local_privacy(true);
        break;
    case ESP_GATTC_CONNECT_EVT:
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT");
        break;
    case ESP_GATTC_OPEN_EVT:
        if (param->open.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "open failed, error status = %x", p_data->open.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "open success");
        gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id;
        memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t));
        ESP_LOGI(GATTC_TAG, "REMOTE BDA:");
        esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t));
        esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id);
        if (mtu_ret){
            ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
        }
        break;
    case ESP_GATTC_CFG_MTU_EVT:
        if (param->cfg_mtu.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status);
        }
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
        break;
    case ESP_GATTC_DIS_SRVC_CMPL_EVT:
        if (param->dis_srvc_cmpl.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "discover service failed, status %d", param->dis_srvc_cmpl.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "discover service complete conn_id %d", param->dis_srvc_cmpl.conn_id);
        esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid);
        break;
    case ESP_GATTC_SEARCH_RES_EVT: {
        ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
        ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id);
        if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) {
            ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
            get_service = true;
            gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
            gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
        }
        break;
    }
    case ESP_GATTC_SEARCH_CMPL_EVT:
        if (p_data->search_cmpl.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status);
            break;
        }
        if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) {
            ESP_LOGI(GATTC_TAG, "Get service information from remote device");
        } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) {
            ESP_LOGI(GATTC_TAG, "Get service information from flash");
        } else {
            ESP_LOGI(GATTC_TAG, "unknown service source");
        }
        if (get_service){
            uint16_t count  = 0;
            uint16_t offset = 0;
            esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if,
                                                                        gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                                        ESP_GATT_DB_CHARACTERISTIC,
                                                                        gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
                                                                        gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
                                                                        INVALID_HANDLE,
                                                                        &count);
            if (ret_status != ESP_GATT_OK){
                ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__);
            }
            if (count > 0){
                char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
                if (!char_elem_result){
                    ESP_LOGE(GATTC_TAG, "gattc no mem");
                }else{
                    ret_status = esp_ble_gattc_get_all_char(gattc_if,
                                                            gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                            gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
                                                            gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
                                                            char_elem_result,
                                                            &count,
                                                            offset);
                    if (ret_status != ESP_GATT_OK){
                        ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_all_char error, %d", __LINE__);
                    }
                    if (count > 0){

                        for (int i = 0; i < count; ++i)
                        {
                            if (char_elem_result[i].uuid.len == ESP_UUID_LEN_16  && (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY))
                            {
                                gl_profile_tab[PROFILE_A_APP_ID].notify_char_handle = char_elem_result[i].char_handle;
                                esp_ble_gattc_register_for_notify (gattc_if,
                                                                   gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
                                                                   char_elem_result[i].char_handle);
                                break;
                            }
                        }
                    }
                }
                free(char_elem_result);
            }
        }

        break;
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
        if (p_data->reg_for_notify.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "reg for notify failed, error status = %x", p_data->reg_for_notify.status);
            break;
        }

            uint16_t count = 0;
            uint16_t offset = 0;
            uint16_t notify_en = 1;
            esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if,
                                                                        gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                                        ESP_GATT_DB_DESCRIPTOR,
                                                                        gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
                                                                        gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
                                                                        p_data->reg_for_notify.handle,
                                                                        &count);
            if (ret_status != ESP_GATT_OK){
                ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__);
            }
            if (count > 0){
                descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
                if (!descr_elem_result){
                    ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem");
                }else{
                    ret_status = esp_ble_gattc_get_all_descr(gattc_if,
                                                             gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                             p_data->reg_for_notify.handle,
                                                             descr_elem_result,
                                                             &count,
                                                             offset);
                if (ret_status != ESP_GATT_OK){
                    ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_all_descr error, %d", __LINE__);
                }

                    for (int i = 0; i < count; ++i)
                    {
                        if (descr_elem_result[i].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[i].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
                        {
                            esp_ble_gattc_write_char_descr (gattc_if,
                                                            gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                            descr_elem_result[i].handle,
                                                            sizeof(notify_en),
                                                            (uint8_t *)&notify_en,
                                                            ESP_GATT_WRITE_TYPE_RSP,
                                                            ESP_GATT_AUTH_REQ_NONE);

                            break;
                        }
                    }
                }
                free(descr_elem_result);
            }

        break;
    }
    case ESP_GATTC_NOTIFY_EVT:
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value:");
        esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len);
        break;
    case ESP_GATTC_WRITE_DESCR_EVT:
        if (p_data->write.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "write descr success");
        break;
    case ESP_GATTC_SRVC_CHG_EVT: {
        esp_bd_addr_t bda;
        memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:");
        esp_log_buffer_hex(GATTC_TAG, bda, sizeof(esp_bd_addr_t));
        break;
    }
    case ESP_GATTC_WRITE_CHAR_EVT:
        if (p_data->write.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "Write char success ");
        break;
    case ESP_GATTC_DISCONNECT_EVT:
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", p_data->disconnect.reason);
        connect = false;
        get_service = false;
        break;
    default:
        break;
    }
}

static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    switch (event) {
    case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
        if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){
            ESP_LOGE(GATTC_TAG, "config local privacy failed, error code =%x", param->local_privacy_cmpl.status);
            break;
        }
        esp_err_t scan_ret = esp_ble_gap_set_ext_scan_params(&ext_scan_params);
        if (scan_ret){
            ESP_LOGE(GATTC_TAG, "set extend scan params error, error code = %x", scan_ret);
        }
        break;
    case ESP_GAP_BLE_SET_EXT_SCAN_PARAMS_COMPLETE_EVT: {
        if (param->set_ext_scan_params.status != ESP_BT_STATUS_SUCCESS) {
            ESP_LOGE(GATTC_TAG, "extend scan parameters set failed, error status = %x", param->set_ext_scan_params.status);
            break;
        }
        //the unit of the duration is second
        esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD);
        break;
    }
    case ESP_GAP_BLE_EXT_SCAN_START_COMPLETE_EVT:
        if (param->ext_scan_start.status != ESP_BT_STATUS_SUCCESS) {
            ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "Scan start success");
        break;
    case ESP_GAP_BLE_PASSKEY_REQ_EVT:                           /* passkey request event */
        /* Call the following function to input the passkey which is displayed on the remote device */
        //esp_ble_passkey_reply(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true, 0x00);
        ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT");
        break;
    case ESP_GAP_BLE_OOB_REQ_EVT: {
        ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_OOB_REQ_EVT");
        uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
        esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
        break;
    }
    case ESP_GAP_BLE_LOCAL_IR_EVT:                               /* BLE local IR event */
        ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT");
        break;
    case ESP_GAP_BLE_LOCAL_ER_EVT:                               /* BLE local ER event */
        ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT");
        break;
    case ESP_GAP_BLE_SEC_REQ_EVT:
        /* send the positive(true) security response to the peer device to accept the security request.
        If not accept the security request, should send the security response with negative(false) accept value*/
        esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
        break;
    case ESP_GAP_BLE_NC_REQ_EVT:
        /* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
        show the passkey number to the user to confirm it with the number displayed by peer device. */
        esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
        ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%" PRIu32, param->ble_security.key_notif.passkey);
        break;
    case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:  ///the app will receive this evt when the IO  has Output capability and the peer device IO has Input capability.
        ///show the passkey number to the user to input it in the peer device.
        ESP_LOGI(GATTC_TAG, "The passkey Notify number:%06" PRIu32, param->ble_security.key_notif.passkey);
        break;
    case ESP_GAP_BLE_KEY_EVT:
        //shows the ble key info share with peer device to the user.
        ESP_LOGI(GATTC_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
        break;
    case ESP_GAP_BLE_AUTH_CMPL_EVT: {
        esp_bd_addr_t bd_addr;
        memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
        ESP_LOGI(GATTC_TAG, "remote BD_ADDR: %08x%04x",\
                (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
                (bd_addr[4] << 8) + bd_addr[5]);
        ESP_LOGI(GATTC_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
        ESP_LOGI(GATTC_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
        if (!param->ble_security.auth_cmpl.success) {
            ESP_LOGI(GATTC_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
        } else {
            ESP_LOGI(GATTC_TAG, "auth mode = %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode));
        }
        break;
    }
    case ESP_GAP_BLE_EXT_ADV_REPORT_EVT: {
        uint8_t *adv_name = NULL;
        uint8_t adv_name_len = 0;
        if(param->ext_adv_report.params.event_type & ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY) {
            ESP_LOGI(GATTC_TAG, "legacy adv, adv type 0x%x data len %d", param->ext_adv_report.params.event_type, param->ext_adv_report.params.adv_data_len);
        } else {
            ESP_LOGI(GATTC_TAG, "extend adv, adv type 0x%x data len %d", param->ext_adv_report.params.event_type, param->ext_adv_report.params.adv_data_len);
        }
        adv_name = esp_ble_resolve_adv_data(param->ext_adv_report.params.adv_data,
                                            ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
        if (!connect && strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) {
            connect = true;
            esp_ble_gap_stop_ext_scan();
            esp_log_buffer_hex("adv addr", param->ext_adv_report.params.addr, 6);
            esp_log_buffer_char("adv name", adv_name, adv_name_len);
            ESP_LOGI(GATTC_TAG, "Stop extend scan and create aux open, primary_phy %d secondary phy %d\n", param->ext_adv_report.params.primary_phy, param->ext_adv_report.params.secondly_phy);

            esp_ble_gap_prefer_ext_connect_params_set(param->ext_adv_report.params.addr,
                                                     ESP_BLE_GAP_PHY_1M_PREF_MASK | ESP_BLE_GAP_PHY_2M_PREF_MASK | ESP_BLE_GAP_PHY_CODED_PREF_MASK ,
                                                     &phy_1m_conn_params, &phy_2m_conn_params, &phy_coded_conn_params);
            esp_ble_gattc_aux_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
                                    param->ext_adv_report.params.addr,
                                    param->ext_adv_report.params.addr_type, true);
        }

        break;
    }
    case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT:
        if (param->ext_scan_stop.status != ESP_BT_STATUS_SUCCESS){
            ESP_LOGE(GATTC_TAG, "extend Scan stop failed, error status = %x", param->ext_scan_stop.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "Stop extend scan successfully");
        break;

    default:
        break;
    }
}

static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
    ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if);

    /* If event is register event, store the gattc_if for each profile */
    if (event == ESP_GATTC_REG_EVT) {
        if (param->reg.status == ESP_GATT_OK) {
            gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
        } else {
            ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d",
                    param->reg.app_id,
                    param->reg.status);
            return;
        }
    }

    /* If the gattc_if equal to profile A, call profile A cb handler,
     * so here call each profile's callback */
    do {
        int idx;
        for (idx = 0; idx < PROFILE_NUM; idx++) {
            if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
                    gattc_if == gl_profile_tab[idx].gattc_if) {
                if (gl_profile_tab[idx].gattc_cb) {
                    gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
                }
            }
        }
    } while (0);
}

void app_main(void)
{
    // Initialize NVS.
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK( ret );

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret) {
        ESP_LOGE(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret) {
        ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_bluedroid_init();
    if (ret) {
        ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_bluedroid_enable();
    if (ret) {
        ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    //register the  callback function to the gap module
    ret = esp_ble_gap_register_callback(esp_gap_cb);
    if (ret){
        ESP_LOGE(GATTC_TAG, "%s gap register error, error code = %x\n", __func__, ret);
        return;
    }

    //register the callback function to the gattc module
    ret = esp_ble_gattc_register_callback(esp_gattc_cb);
    if(ret){
        ESP_LOGE(GATTC_TAG, "%s gattc register error, error code = %x\n", __func__, ret);
        return;
    }

    ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
    if (ret){
        ESP_LOGE(GATTC_TAG, "%s gattc app register error, error code = %x\n", __func__, ret);
    }

    ret = esp_ble_gatt_set_local_mtu(200);
    if (ret){
        ESP_LOGE(GATTC_TAG, "set local  MTU failed, error code = %x", ret);
    }

    /* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
    esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;     //bonding with peer device after authentication
    esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;           //set the IO capability to No output No input
    uint8_t key_size = 16;      //the key size should be 7~16 bytes
    uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
    uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
    uint8_t oob_support = ESP_BLE_OOB_DISABLE;
    esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
    /* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
    and the response key means which key you can distribute to the Master;
    If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
    and the init key means which key you can distribute to the slave. */
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
    esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));

}

#elif 0 //串口与虚拟串口;//uart_types.h//uart.h

#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/errno.h>
#include <sys/unistd.h>
#include <sys/select.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "sdkconfig.h"


#define ECHO_TEST_TXD GPIO_NUM_7
#define ECHO_TEST_RXD GPIO_NUM_6
#define ECHO_TEST_RTS (UART_PIN_NO_CHANGE)
#define ECHO_TEST_CTS (UART_PIN_NO_CHANGE)
#define ECHO_UART_PORT_NUM      UART_NUM_1
#define ECHO_UART_BAUD_RATE     115200
#define ECHO_TASK_STACK_SIZE    (CONFIG_EXAMPLE_TASK_STACK_SIZE)

static const char* TAG = "uart";
#define BUF_SIZE (16)

static void echo_task(void *arg)
{
    /* 配置UART驱动参数,
     * 通讯引脚并安装驱动程序 */
    uart_config_t uart_config = {
        .baud_rate = ECHO_UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    int intr_alloc_flags = 0;

#if CONFIG_UART_ISR_IN_IRAM
    intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif

    ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
    ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));

    // 为传入数据配置临时缓冲区
    uint8_t *data = (uint8_t *) malloc(BUF_SIZE);

    while (1) {
        // 从UART读取数据
        int len = uart_read_bytes(ECHO_UART_PORT_NUM, data, (BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
        //将数据写回UART
        /*
        uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) data, len);
        if (len) {
            data[len] = '\0';
            ESP_LOGI(TAG, "Recv str: %s", (char *) data);
        }
        */
       if(len>0)
       {
        data[len] = '\0';
        ESP_LOGI(TAG, "Recv str: %s", (char *) data);
       }
       //uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) data, len);
       
    }
}

void app_main(void)
{
    xTaskCreate(echo_task, "uart_echo_task", 1024*4, NULL, 10, NULL);
    while (1)
    {
        vTaskDelay(6000 / portTICK_PERIOD_MS);
    }
    
    
}

#elif 0 // 中断接收串口数据
//配置UART硬件

#include "driver/uart.h"

void uart_init() {
    // 配置UART参数
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    
    // 设置UART参数
    uart_param_config(UART_NUM_1, &uart_config);
    
    // 设置UART引脚
    uart_set_pin(UART_NUM_1, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    
    // 安装UART驱动
    uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
}

//创建中断处理函数:

void uart_event_task(void *pvParameters) {
    uart_event_t event;
    size_t buffered_size;
    uint8_t* dtmp = (uint8_t*) malloc(RX_BUF_SIZE+1);
    
    while (true) {
        // 等待UART事件
        if(xQueueReceive(uart0_queue, (void *)&event, (portTickType)portMAX_DELAY)) {
            bzero(dtmp, RX_BUF_SIZE);
            // 处理不同的事件类型
            switch (event.type) {
                // 发送完成事件
                case UART_DATA:
                    uart_read_bytes(UART_NUM_1, dtmp, RX_BUF_SIZE, portMAX_DELAY);
                    // 处理接收到的数据
                    printf("Received data: %s", dtmp);
                    break;
                // 其他事件 ...
                
                default:
                    break;
            }
            
            uart_get_buffered_data_len(UART_NUM_1, &buffered_size);
            ESP_LOGI(TAG, "uart[%d] event:", UART_NUM_1);
            if (buffered_size) {
                ESP_LOGI(TAG, "There are %d bytes in the UART buffer", buffered_size);
            }
            if (event.size) {
                ESP_LOGI(TAG, "Received %d bytes from UART", event.size);
                printf("Data:%s");
                
            }
        }
    }
    free(dtmp);
    dtmp = NULL;
    vTaskDelete(NULL);
}

//配置中断处理程序

#include "driver/uart.h"
// ...
// UART事件队列
QueueHandle_t uart0_queue;

void uart_init() {
    // ...
    // 创建UART事件队列
    uart0_queue = xQueueCreate(10, sizeof(uart_event_t));
    // 安装UART中断服务程序
    uart_isr_register(UART_NUM_1, uart_isr, NULL, 0, &handle_console);
    // 启用UART中断
    uart_enable_rx_intr(UART_NUM_1);
}







#endif

/*idf.py partition-table-flash ：使用 esptool.py 工具烧写分区表。

idf.py flash ：会烧写所有内容，包括分区表。
分区表的更新并不会擦除根据旧分区表存储的数据。
此时，您可以使用 idf.py erase-flash 命令或者 esptool.py erase_flash 命令来擦除 flash 中的所有内容。
 1.  parttool.py
 2.  idf.py erase-flash
*




分区表的地址（默认为0x8000）。允许您移动分区表，它为引导加载程序提供更多空间。
请注意，引导加载程序和应用程序都需要使用相同的 PARTITION_TABLE_OFFSET 值进行编译。
该数字应该是 0x1000 的倍数。请注意，如果该值设置为更高的值，则可能需要更改分区表 CSV 文件中的分区偏移量。
要使每个分区偏移量适应配置的分区表偏移量，请将 CSV 文件中的所有分区偏移量留空。
*/